diff --git a/package-lock.json b/package-lock.json index 8acd7616..1c8f42c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16313,6 +16313,7 @@ "dependencies": { "chalk": "^4.1.2", "cwd": "^0.10.0", + "filenamify": "^4.3.0", "jest-dev-server": "^7.0.0", "jest-environment-node": "^29.4.1", "merge-deep": "^3.0.3" @@ -16325,6 +16326,60 @@ "node": ">=14.0.0" } }, + "packages/jest-environment-puppeteer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "packages/jest-environment-puppeteer/node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "engines": { + "node": ">=4" + } + }, + "packages/jest-environment-puppeteer/node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/jest-environment-puppeteer/node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "packages/jest-environment-puppeteer/node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "packages/jest-puppeteer": { "version": "7.0.0", "dependencies": { @@ -24570,11 +24625,50 @@ "requires": { "chalk": "^4.1.2", "cwd": "^0.10.0", + "filenamify": "^4.3.0", "jest-dev-server": "^7.0.0", "jest-environment-node": "^29.4.1", "merge-deep": "^3.0.3", "rollup": "^3.13.0", "rollup-plugin-swc3": "^0.8.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==" + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "requires": { + "escape-string-regexp": "^1.0.2" + } + } } }, "jest-get-type": { diff --git a/packages/jest-environment-puppeteer/package.json b/packages/jest-environment-puppeteer/package.json index ab86babc..3431bb75 100644 --- a/packages/jest-environment-puppeteer/package.json +++ b/packages/jest-environment-puppeteer/package.json @@ -44,6 +44,7 @@ "dependencies": { "chalk": "^4.1.2", "cwd": "^0.10.0", + "filenamify": "^4.3.0", "jest-dev-server": "^7.0.0", "jest-environment-node": "^29.4.1", "merge-deep": "^3.0.3" diff --git a/packages/jest-environment-puppeteer/src/env.js b/packages/jest-environment-puppeteer/src/env.js index c2994be6..fd2eb3ac 100644 --- a/packages/jest-environment-puppeteer/src/env.js +++ b/packages/jest-environment-puppeteer/src/env.js @@ -1,9 +1,10 @@ // eslint-disable-next-line -import { join } from "node:path"; +import { join, basename } from "node:path"; import NodeEnvironment from "jest-environment-node"; import chalk from "chalk"; import { mkdir } from "node:fs/promises"; import { readConfig, getPuppeteer } from "./readConfig"; +import filenamify from "filenamify"; const handleError = (error) => { process.emit("uncaughtException", error); @@ -20,9 +21,38 @@ const getWorkerIndex = () => process.env.JEST_WORKER_ID - 1; const getEndpointIndex = () => Math.min(+process.env.BROWSERS_COUNT - 1, getWorkerIndex()); -// const screenshotsDirectory = join(process.cwd(), "screenshots"); +const screenshotsDirectory = join(process.cwd(), "screenshots"); + +const screenshot = async (page, name) => { + await mkdir(screenshotsDirectory, { recursive: true }); + const filename = filenamify(`${name} (failed).jpg`); + await page.screenshot({ + path: join(screenshotsDirectory, filename), + }); +}; + +const getFullTestName = (test) => { + const names = []; + let current = test; + while (current) { + if (current.name === "ROOT_DESCRIBE_BLOCK") break; + names.unshift(current.name); + current = current.parent; + } + return names.join(" "); +}; + +const getErrorScreenshotName = (test, filename) => { + const fullName = getFullTestName(test); + if (!filename) return fullName; + return `${basename(filename)} - ${fullName}`; +}; class PuppeteerEnvironment extends NodeEnvironment { + constructor(config, context) { + super(config, context); + this.testPath = context.testPath; + } // Jest is not available here, so we have to reverse engineer // the setTimeout function, see https://github.com/facebook/jest/blob/v23.1.0/packages/jest-runtime/src/index.js#L823 setTimeout(timeout) { @@ -34,14 +64,14 @@ class PuppeteerEnvironment extends NodeEnvironment { } } - // async handleTestEvent(event, state) { - // if (event.name === "test_fn_failure") { - // const testName = state.currentlyRunningTest.name; - // await this.global.page.screenshot({ - // path: join(screenshotsDirectory, `${testName}.jpg`), - // }); - // } - // } + async handleTestEvent(event, state) { + if (event.name === "test_fn_failure") { + await screenshot( + this.global.page, + getErrorScreenshotName(state.currentlyRunningTest, this.testPath) + ); + } + } async setup() { const config = await readConfig(); @@ -169,7 +199,6 @@ class PuppeteerEnvironment extends NodeEnvironment { }; await this.global.jestPuppeteer.resetBrowser(); - await mkdir(join(process.cwd(), "screenshots"), { recursive: true }); } async teardown() {