From 25c1f6c0b4bf2635042004e254db612211a305ae Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 6 Jun 2023 03:23:27 +0900 Subject: [PATCH 1/4] update env loading, optimize, renames --- testing/report/report.config.js | 14 +++++--- testing/report/src/index.ts | 63 ++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/testing/report/report.config.js b/testing/report/report.config.js index d66aa52b..2eba836d 100644 --- a/testing/report/report.config.js +++ b/testing/report/report.config.js @@ -1,13 +1,17 @@ -const env = require("dotenv").config(); +const path = require("path"); -const { OUTDIR, LOCAL_ARCHIVE_FILE, LOCAL_ARCHIVE_IMAGE } = env; +require("dotenv").config({ + path: path.join(__dirname, ".env"), +}); + +const { OUTDIR, LOCAL_ARCHIVE_FILES, LOCAL_ARCHIVE_IMAGES } = process.env; module.exports = { - sample: "../../data/figma-archives/dev/meta.json", + sample: path.join(__dirname, "../../data/figma-archives/prod/meta.json"), outDir: OUTDIR, localarchive: { - file: LOCAL_ARCHIVE_FILE, - image: LOCAL_ARCHIVE_IMAGE, + files: LOCAL_ARCHIVE_FILES, + images: LOCAL_ARCHIVE_IMAGES, }, skipIfReportExists: true, }; diff --git a/testing/report/src/index.ts b/testing/report/src/index.ts index 5cd118c8..5e6116b9 100644 --- a/testing/report/src/index.ts +++ b/testing/report/src/index.ts @@ -26,35 +26,47 @@ interface ReportConfig { sample: string; outDir?: string; localarchive?: { - file: string; - image: string; + files: string; + images: string; }; skipIfReportExists?: boolean; } // disable logging console.log = () => {}; -console.info = () => {}; console.warn = () => {}; console.error = () => {}; async function report() { + console.info("Starting report"); const cwd = process.cwd(); // read the config const config: ReportConfig = require(path.join(cwd, "report.config.js")); // load the sample file - const samples_path = path.join(cwd, config.sample); + const samples_path = (await exists(config.sample)) + ? config.sample + : path.join(cwd, config.sample); + + assert( + await exists(samples_path), + `sample file not found at ${config.sample} nor ${samples_path}` + ); + const samples = JSON.parse(await fs.readFile(samples_path, "utf-8")); // create .coverage folder const coverage_path = config.outDir ?? path.join(cwd, ".coverage"); + + console.info(`Loaded ${samples.length} samples`); + console.info(`Configuration used - ${JSON.stringify(config, null, 2)}`); + mkdir(coverage_path); const client = Client({ paths: { - file: config.localarchive.file, - image: config.localarchive.image, + files: config.localarchive.files, + images: config.localarchive.images, }, }); @@ -93,7 +105,7 @@ async function report() { }) ).data.images; } catch (e) { - console.error("exports not ready for", filekey); + console.error("exports not ready for", filekey, e.message); continue; } @@ -134,6 +146,26 @@ async function report() { ); try { + // image A (original) + const exported = exports[frame.id]; + const image_a_rel = "./a.png"; + const image_a = path.join(coverage_node_path, image_a_rel); + // download the exported image with url + // if the exported is local fs path, then use copy instead + if (exists(exported)) { + // copy file with symlink + // unlink if exists + if (exists(image_a)) { + await fs.unlink(image_a); + } + await fs.symlink(exported, image_a); + } else if (exported.startsWith("http")) { + const dl = await axios.get(exported, { responseType: "arraybuffer" }); + await fs.writeFile(image_a, dl.data); + } else { + throw new Error(`File not found - ${exported}`); + } + // codegen const code = await htmlcss( { @@ -165,23 +197,6 @@ async function report() { const image_b = path.join(coverage_node_path, image_b_rel); await fs.writeFile(image_b, screenshot_buffer); - const exported = exports[frame.id]; - const image_a_rel = "./a.png"; - const image_a = path.join(coverage_node_path, image_a_rel); - // download the exported image with url - // if the exported is local fs path, then use copy instead - if (exists(exported)) { - // copy file with symlink - // unlink if exists - if (exists(image_a)) { - await fs.unlink(image_a); - } - await fs.symlink(exported, image_a); - } else { - const dl = await axios.get(exported, { responseType: "arraybuffer" }); - await fs.writeFile(image_a, dl.data); - } - const diff = await resemble(image_a, image_b); const diff_file = path.join(coverage_node_path, "diff.png"); // write diff.png From 0c251da9e99843089ce0f0fdb05860e375ae54cb Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 6 Jun 2023 13:43:48 +0900 Subject: [PATCH 2/4] exception handling & improed logging --- testing/report/src/index.ts | 60 +++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/testing/report/src/index.ts b/testing/report/src/index.ts index 5e6116b9..0a27e522 100644 --- a/testing/report/src/index.ts +++ b/testing/report/src/index.ts @@ -1,6 +1,5 @@ import path from "path"; import fs from "fs/promises"; -import { existsSync as exists } from "fs"; import assert from "assert"; import ora from "ora"; import { mapper } from "@design-sdk/figma-remote"; @@ -20,7 +19,17 @@ import { RemoteImageRepositories } from "@design-sdk/figma-remote/asset-reposito setupCache(axios); -const mkdir = (path: string) => !exists(path) && fs.mkdir(path); +const exists = async (path: string) => { + try { + await fs.access(path); + return true; + } catch (e) { + return false; + } +}; + +const mkdir = async (path: string) => + !(await exists(path)) && (await fs.mkdir(path)); interface ReportConfig { sample: string; @@ -61,7 +70,7 @@ async function report() { console.info(`Loaded ${samples.length} samples`); console.info(`Configuration used - ${JSON.stringify(config, null, 2)}`); - mkdir(coverage_path); + await mkdir(coverage_path); const client = Client({ paths: { @@ -73,10 +82,12 @@ async function report() { const ssworker = new ScreenshotWorker({}); await ssworker.launch(); + let i = 0; for (const c of samples) { + i++; // create .coverage/:id folder const coverage_set_path = path.join(coverage_path, c.id); - mkdir(coverage_set_path); + await mkdir(coverage_set_path); const { id: filekey } = c; let file; @@ -109,17 +120,22 @@ async function report() { continue; } + let ii = 0; for (const frame of frames) { - const spinner = ora(`Running coverage for ${c.id}/${frame.id}`).start(); + ii++; + + const spinner = ora( + `[${i}/${samples.length}] Running coverage for ${c.id}/${frame.id} (${ii}/${frames.length})` + ).start(); // create .coverage/:id/:node folder const coverage_node_path = path.join(coverage_set_path, frame.id); - mkdir(coverage_node_path); + await mkdir(coverage_node_path); // report.json const report_file = path.join(coverage_node_path, "report.json"); if (config.skipIfReportExists) { - if (exists(report_file)) { + if (await exists(report_file)) { spinner.succeed(`Skipping - report for ${frame.id} already exists`); continue; } @@ -152,13 +168,19 @@ async function report() { const image_a = path.join(coverage_node_path, image_a_rel); // download the exported image with url // if the exported is local fs path, then use copy instead - if (exists(exported)) { - // copy file with symlink - // unlink if exists - if (exists(image_a)) { - await fs.unlink(image_a); + if (await exists(exported)) { + try { + // copy file with symlink + // rempve if already exists before linking new one + if (await exists(image_a)) { + await fs.unlink(image_a); + } + await fs.symlink(exported, image_a); + } catch (e) { + // TODO: symlink still fails with "EEXIST: file already exists, symlink" + // we need to handle this. + // reason? - unknown } - await fs.symlink(exported, image_a); } else if (exported.startsWith("http")) { const dl = await axios.get(exported, { responseType: "arraybuffer" }); await fs.writeFile(image_a, dl.data); @@ -166,6 +188,11 @@ async function report() { throw new Error(`File not found - ${exported}`); } + if (!(await exists(image_a))) { + spinner.fail(`Image A not found - ${image_a}`); + continue; + } + // codegen const code = await htmlcss( { @@ -239,6 +266,13 @@ async function report() { spinner.fail(`error on ${frame.id} : ${e.message}`); } } + + // cleaup + // if the coverage is empty, remove the folder + const files = await fs.readdir(coverage_set_path); + if (files.length === 0) { + await fs.rmdir(coverage_set_path); + } } // cleaup From 585b2515fdc40091c5008f3a93045fc0b70559c5 Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 6 Jun 2023 14:04:30 +0900 Subject: [PATCH 3/4] add respawn on pupetter --- testing/testing-screenshot/index.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/testing/testing-screenshot/index.ts b/testing/testing-screenshot/index.ts index c8729a96..0b362194 100644 --- a/testing/testing-screenshot/index.ts +++ b/testing/testing-screenshot/index.ts @@ -40,7 +40,17 @@ export class Worker { return this.browser; } + async relaunch() { + await this.close(); + return this.launch(); + } + async screenshot({ htmlcss, viewport }: ScreenshotOptions) { + // check if page is available (try to avoid TargetClosedError) + if (!this.page || this.page.isClosed()) { + await this.relaunch(); + } + this.page.setViewport(viewport); await this.page.setContent(htmlcss, { waitUntil: "networkidle0" }); const buffer = await this.page.screenshot({ @@ -52,7 +62,11 @@ export class Worker { } async close() { - await this.browser.close(); + try { + await this.browser.close(); + } catch (e) {} + this.browser = null; + this.page = null; } terminate() { From 3e61f7ce9be44c35cb3914a856d3b590ec8f676b Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 6 Jun 2023 22:07:18 +0900 Subject: [PATCH 4/4] add pupeteer resapwn --- testing/testing-screenshot/index.ts | 57 +++++++++++++++++------------ 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/testing/testing-screenshot/index.ts b/testing/testing-screenshot/index.ts index 0b362194..a3ddc7d2 100644 --- a/testing/testing-screenshot/index.ts +++ b/testing/testing-screenshot/index.ts @@ -8,14 +8,6 @@ interface ScreenshotOptions { }; } -export async function screenshot({ htmlcss, viewport }: ScreenshotOptions) { - const worker = new Worker({}); - await worker.launch(); - const buffer = worker.screenshot({ htmlcss, viewport }); - await worker.terminate(); - return buffer; -} - export class Worker { private browser: Browser; private page: Page; @@ -46,30 +38,47 @@ export class Worker { } async screenshot({ htmlcss, viewport }: ScreenshotOptions) { - // check if page is available (try to avoid TargetClosedError) - if (!this.page || this.page.isClosed()) { + try { + if (!this.browser || !this.page || this.page.isClosed()) { + await this.relaunch(); + } + await this.page.setViewport(viewport); + await this.page.setContent(htmlcss, { waitUntil: "networkidle0" }); + const buffer = await this.page.screenshot({ + type: "png", + // support transparency + omitBackground: true, + }); + return buffer; + } catch (error) { + console.log(`Failed to take screenshot: ${error.message}`); await this.relaunch(); + // After relaunch, retry taking screenshot or rethrow the error + return this.screenshot({ htmlcss, viewport }); } - - this.page.setViewport(viewport); - await this.page.setContent(htmlcss, { waitUntil: "networkidle0" }); - const buffer = await this.page.screenshot({ - type: "png", - // support transparency - omitBackground: true, - }); - return buffer; } async close() { - try { - await this.browser.close(); - } catch (e) {} - this.browser = null; - this.page = null; + if (this.browser) { + try { + await this.browser.close(); + } catch (e) { + console.log(`Failed to close browser: ${e.message}`); + } + this.browser = null; + this.page = null; + } } terminate() { this.close(); } } + +export async function screenshot(options: ScreenshotOptions) { + const worker = new Worker({}); + await worker.launch(); + const buffer = await worker.screenshot(options); + await worker.terminate(); + return buffer; +}