Files
MapLibreParticleLab/scripts/aws-device-farm/store-test-artifacts.mjs
2025-05-09 13:33:46 +00:00

124 lines
3.8 KiB
JavaScript

import * as fs from "node:fs";
import { Readable } from "node:stream";
import { finished } from "node:stream/promises";
import * as path from "node:path";
import { parseArgs } from "node:util";
import { ArtifactType, ListArtifactsCommand, ListJobsCommand, ListSuitesCommand } from "@aws-sdk/client-device-farm";
import { getDeviceFarmClient } from "./device-farm-client.mjs";
/**
* @returns {never}
*/
function usage() {
console.error("Stores artifacts from AWS Device Farm run");
console.error("Usage: node store-test-artifacts.mjs --outputDir OUTPUT_DIR --runArn RUN_ARN");
console.error("Arguments:")
console.error("--customerArtifacts: only download customer artifacts");
console.error("--testsSuite: only download stuff from Tests Suite");
process.exit(1);
}
function getArgs() {
const {
values
} = parseArgs({
options: {
outputDir: {
type: "string",
},
runArn: {
type: "string",
multiple: true
},
testsSuite: {
type: "boolean"
},
customerArtifacts: {
type: "boolean"
}
},
});
const { outputDir, runArn } = values;
if (typeof outputDir !== 'string') usage();
if (!runArn || !runArn.length) usage();
function suitesFilter() {
const names = new Set();
if (values.testsSuite) names.add("Tests Suite");
if (names.size === 0) return () => true;
return (/** @type {string} **/ name) => names.has(name);
}
/** @type {() => ArtifactType[]} **/
function getArtifactsToDownload() {
if (values.customerArtifacts) return ["CUSTOMER_ARTIFACT"];
return ['TESTSPEC_OUTPUT', 'CUSTOMER_ARTIFACT', 'CUSTOMER_ARTIFACT_LOG', 'DEVICE_LOG'];
}
return {
outputDir,
runArn,
suitesFilter,
artifactsToDownload: getArtifactsToDownload()
}
}
const { outputDir, runArn, suitesFilter, artifactsToDownload } = getArgs();
if (!fs.existsSync(outputDir)) {
console.error("Output dir does not exist");
process.exit(1);
}
const deviceFarmClient = getDeviceFarmClient();
await storeRunArtifacts(runArn, outputDir);
///////////////////////////////////////////////////////////////////////////////
/**
* Looks for the run with the provided ARN and returns the test spec output.
*
* @param {string[]} arnArr
* @param {string} outputDir
* @returns string
*/
async function storeRunArtifacts(arnArr, outputDir) {
for (const arn of arnArr) {
const jobs = await deviceFarmClient.send(new ListJobsCommand({
arn
}));
await Promise.all((jobs.jobs || []).map(async (job) => {
const suites = await deviceFarmClient.send(new ListSuitesCommand({ arn: job.arn }));
await Promise.all((suites.suites || []).filter(suitesFilter).map(async (suite) => {
const artifacts = await deviceFarmClient.send(new ListArtifactsCommand({
arn: suite.arn,
type: 'FILE'
}));
await Promise.all((artifacts.artifacts || []).map(async (artifact) => {
if (!artifact.name || !artifact.url || !artifact.type) return;
if (artifactsToDownload.includes(artifact.type)) {
if (!artifact.arn) return;
const destination = path.join(outputDir, ...[job.device?.name || ""].filter(x => x), `${Buffer.from(artifact.arn).toString('base64')}.${artifact.extension}`);
await fs.promises.mkdir(path.dirname(destination), { recursive: true });
try {
await fs.promises.access(destination);
return; // already exists
} catch (err) {
}
const res = await fetch(artifact.url);
if (!res.ok || !res.body) return;
const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
await finished(Readable.fromWeb(/** @type {any} **/(res.body)).pipe(fileStream));
}
}));
}));
}));
}
console.log(`Wrote run artifacts to ${outputDir}`)
}