@chromatic-com/playwright 0.11.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ var da=Object.defineProperty;var s=(t,a)=>da(t,"name",{value:a,configurable:!0})
|
|
|
23
23
|
// so for now it is being passed as a string until that can be resolved.
|
|
24
24
|
const doPostProcessing = (rrwebSnapshotInstance, documentToSnapshot) => {
|
|
25
25
|
return new Promise((resolve) => {
|
|
26
|
-
const domSnapshot = rrwebSnapshotInstance.snapshot(documentToSnapshot);
|
|
26
|
+
const domSnapshot = rrwebSnapshotInstance.snapshot(documentToSnapshot, { recordCanvas: true });
|
|
27
27
|
// do some post-processing on the snapshot
|
|
28
28
|
const toDataURL = async (url) => {
|
|
29
29
|
// read contents of the blob URL
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/makeTest.ts","../src/takeSnapshot.ts","../src/createResourceArchive.ts"],"names":["test","base","expect","join","readFileSync","dedent","rrweb","require","resolve","chromaticSnapshots","Map","takeSnapshot","page","nameOrTestInfo","maybeTestInfo","name","testId","Error","has","get","size","number","on","msg","logger","log","text","domSnapshot","evaluate","bufferedSnapshot","Buffer","from","JSON","stringify","set","idle","__name","networkTimeoutMs","DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS","globalNetworkTimerId","globalNetworkResolver","globalNetworkTimeout","Promise","setTimeout","warn","networkIdlePromise","waitForLoadState","finally","clearTimeout","race","createResourceArchive","networkTimeout","assetDomains","httpCredentials","cdpClient","context","newCDPSession","resourceArchiver","ResourceArchiver","watch","archive","performChromaticSnapshot","delay","diffIncludeAntiAliasing","diffThreshold","disableAutoSnapshot","forcedColors","pauseAnimationAtEnd","prefersReducedMotion","resourceArchiveTimeout","cropToViewport","ignoreSelectors","use","testInfo","project","trackRun","browser","browserType","completeArchive","resourceArchive","snapshots","chromaticStorybookParams","outputDir","writeTestResult","pageUrl","url","viewport","viewportSize","Object","fromEntries","trackComplete","delete","makeTest","extend","undefined","option","chromaticSnapshot","auto"],"mappings":"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","sourcesContent":["import { test as base, expect } from '@playwright/test';\n\nimport { makeTest } from './makeTest';\n\nexport const test = makeTest(base);\nexport { expect };\n\nexport { takeSnapshot } from './takeSnapshot';\nexport type { ChromaticConfig } from '@chromatic-com/shared-e2e';\n","import type {\n TestType,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestInfo,\n Page,\n} from '@playwright/test';\nimport type { ChromaticConfig } from '@chromatic-com/shared-e2e';\nimport {\n writeTestResult,\n trackComplete,\n trackRun,\n DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS,\n} from '@chromatic-com/shared-e2e';\nimport { join } from 'node:path';\nimport { chromaticSnapshots, takeSnapshot } from './takeSnapshot';\nimport { createResourceArchive } from './createResourceArchive';\n\nexport const performChromaticSnapshot = async (\n {\n page,\n delay,\n diffIncludeAntiAliasing,\n diffThreshold,\n disableAutoSnapshot,\n forcedColors,\n pauseAnimationAtEnd,\n prefersReducedMotion,\n resourceArchiveTimeout,\n assetDomains,\n cropToViewport,\n ignoreSelectors,\n }: ChromaticConfig & { page: Page },\n use: () => Promise<void>,\n testInfo: TestInfo\n) => {\n const { testId, project } = testInfo;\n const httpCredentials = project?.use?.httpCredentials;\n\n try {\n trackRun();\n\n // CDP only works in Chromium, so we only capture archives in Chromium.\n // We can later snapshot them in different browsers in the cloud.\n // TODO: I'm not sure if this is the best way to detect the browser version, but\n // it seems to work\n if (page.context().browser().browserType().name() !== 'chromium') {\n await use();\n return;\n }\n\n const completeArchive = await createResourceArchive({\n page,\n networkTimeout: resourceArchiveTimeout,\n assetDomains,\n httpCredentials,\n });\n await use();\n\n if (!disableAutoSnapshot) {\n await takeSnapshot(page, testInfo);\n }\n\n const resourceArchive = await completeArchive();\n const snapshots: Map<string, Buffer> = chromaticSnapshots.get(testId) || new Map();\n\n const chromaticStorybookParams = {\n ...(delay && { delay }),\n ...(diffIncludeAntiAliasing && { diffIncludeAntiAliasing }),\n ...(diffThreshold && { diffThreshold }),\n ...(forcedColors && { forcedColors }),\n ...(pauseAnimationAtEnd && { pauseAnimationAtEnd }),\n ...(prefersReducedMotion && { prefersReducedMotion }),\n ...(cropToViewport && { cropToViewport }),\n ...(ignoreSelectors && { ignoreSelectors }),\n };\n\n // TestInfo.outputDir gives us the test-specific subfolder (https://playwright.dev/docs/api/class-testconfig#test-config-output-dir);\n // we want to write one level above that\n const outputDir = join(testInfo.outputDir, '..');\n await writeTestResult(\n { ...testInfo, outputDir, pageUrl: page.url(), viewport: page.viewportSize() },\n Object.fromEntries(snapshots),\n resourceArchive,\n chromaticStorybookParams\n );\n\n trackComplete();\n } finally {\n // make sure we clear the value associated with this test ID, so the shared chromaticSnapshots object stays small\n chromaticSnapshots.delete(testId);\n }\n};\n\n// We do this slightly odd thing (makeTest) to avoid importing playwright multiple times when\n// linking this package. To avoid the main entry, you can:\n//\n// import { makeTest } from '@chromaui/test-archiver/src/playwright-api/makeTest';\n// import { takeSnapshot as snapshot } from '@chromaui/test-archiver/src/playwright-api/takeSnapshot';\nexport const makeTest = (\n base: TestType<\n PlaywrightTestArgs & PlaywrightTestOptions,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n >\n) =>\n base.extend<ChromaticConfig & { chromaticSnapshot: void }>({\n // ChromaticConfig defaults\n delay: [undefined, { option: true }],\n diffIncludeAntiAliasing: [undefined, { option: true }],\n diffThreshold: [undefined, { option: true }],\n disableAutoSnapshot: [false, { option: true }],\n forcedColors: [undefined, { option: true }],\n pauseAnimationAtEnd: [undefined, { option: true }],\n prefersReducedMotion: [undefined, { option: true }],\n resourceArchiveTimeout: [DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS, { option: true }],\n assetDomains: [[], { option: true }],\n cropToViewport: [undefined, { option: true }],\n ignoreSelectors: [undefined, { option: true }],\n\n chromaticSnapshot: [\n performChromaticSnapshot,\n // ensures this fixture runs without having to be explicitly called (https://playwright.dev/docs/test-fixtures#automatic-fixtures)\n { auto: true },\n ],\n });\n","import type { Page, TestInfo } from '@playwright/test';\nimport { readFileSync } from 'fs';\nimport { dedent } from 'ts-dedent';\nimport type { serializedNodeWithId } from '@chromaui/rrweb-snapshot';\nimport { logger } from '@chromatic-com/shared-e2e';\n\nconst rrweb = readFileSync(require.resolve('@chromaui/rrweb-snapshot'), 'utf8');\n\n// top-level key is the test ID, next level key is the name of the snapshot (which we expect to be unique)\nexport const chromaticSnapshots: Map<string, Map<string, Buffer>> = new Map();\n\nasync function takeSnapshot(page: Page, testInfo: TestInfo): Promise<void>;\nasync function takeSnapshot(page: Page, name: string, testInfo: TestInfo): Promise<void>;\nasync function takeSnapshot(\n page: Page,\n nameOrTestInfo: string | TestInfo,\n maybeTestInfo?: TestInfo\n): Promise<void> {\n let name: string;\n let testId: string;\n if (typeof nameOrTestInfo === 'string') {\n if (!maybeTestInfo) throw new Error('Incorrect usage');\n testId = maybeTestInfo.testId;\n name = nameOrTestInfo;\n } else {\n testId = nameOrTestInfo.testId;\n const number = chromaticSnapshots.has(testId) ? chromaticSnapshots.get(testId).size + 1 : 1;\n name = `Snapshot #${number}`;\n }\n\n page.on('console', (msg) => {\n logger.log(`CONSOLE: \"${msg.text()}\"`);\n });\n\n // Serialize and capture the DOM\n const domSnapshot: serializedNodeWithId = await page.evaluate(dedent`\n ${rrweb};\n\n // this code was erroring the page.evaluate() when it was passed as a function to page.evaluate(),\n // so for now it is being passed as a string until that can be resolved.\n const doPostProcessing = (rrwebSnapshotInstance, documentToSnapshot) => {\n return new Promise((resolve) => {\n const domSnapshot = rrwebSnapshotInstance.snapshot(documentToSnapshot);\n // do some post-processing on the snapshot\n const toDataURL = async (url) => {\n // read contents of the blob URL\n const response = await fetch(url);\n const blob = await response.blob();\n return new Promise((resolveFileRead, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolveFileRead(reader.result);\n reader.onerror = reject;\n // convert the blob to base64 string\n reader.readAsDataURL(blob);\n });\n };\n\n const replaceBlobUrls = async (node) => {\n await Promise.all(\n node.childNodes.map(async (childNode) => {\n if (childNode.tagName === 'img' && childNode.attributes.src?.startsWith('blob:')) {\n const base64Url = await toDataURL(childNode.attributes.src);\n // eslint-disable-next-line no-param-reassign\n childNode.attributes.src = base64Url;\n }\n\n if (childNode.childNodes?.length) {\n await replaceBlobUrls(childNode);\n }\n })\n );\n };\n\n replaceBlobUrls(domSnapshot).then(() => {\n resolve(domSnapshot);\n });\n });\n };\n\n // page.evaluate returns the value of the function being evaluated. In this case, it means that\n // it is returning either the resolved value of the Promise or the return value of the call to\n // the snapshot function. See https://playwright.dev/docs/api/class-page#page-evaluate.\n if (typeof define === 'function' && define.amd) {\n // AMD support is detected, so we need to load rrwebSnapshot asynchronously\n new Promise((resolve) => {\n // eslint-disable-next-line import/no-dynamic-require, global-require\n require(['rrwebSnapshot'], (rrwebSnapshot) => {\n doPostProcessing(rrwebSnapshot, document).then((domSnapshot) => {\n resolve(domSnapshot);\n });\n });\n });\n } else {\n new Promise((resolve) => {\n doPostProcessing(rrwebSnapshot, document).then((domSnapshot) => {\n resolve(domSnapshot);\n }); \n });\n } \n `);\n\n const bufferedSnapshot = Buffer.from(JSON.stringify(domSnapshot));\n if (!chromaticSnapshots.has(testId)) {\n // map used so the snapshots are always in order\n chromaticSnapshots.set(testId, new Map());\n }\n chromaticSnapshots.get(testId).set(name, bufferedSnapshot);\n}\n\nexport { takeSnapshot };\n","import type { Page } from 'playwright';\nimport {\n ResourceArchiver,\n ResourceArchive,\n DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS,\n logger,\n HttpCredentials,\n} from '@chromatic-com/shared-e2e';\n\nconst idle = async (page: Page, networkTimeoutMs = DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS) => {\n let globalNetworkTimerId: null | ReturnType<typeof setTimeout> = null;\n let globalNetworkResolver: null | (() => void) = null;\n // XXX_jwir3: The way this works is as follows:\n // There are two promises created here. They wrap two separate timers, and we await on a race of both Promises.\n\n // The first promise wraps a global timeout, where all requests MUST complete before that timeout has passed.\n // If the timeout passes, an error is thrown. This promise can only throw errors, it cannot resolve successfully.\n const globalNetworkTimeout = new Promise<void>((resolve) => {\n globalNetworkResolver = resolve;\n\n globalNetworkTimerId = setTimeout(() => {\n logger.warn(`Global timeout of ${networkTimeoutMs}ms reached`);\n globalNetworkResolver();\n }, networkTimeoutMs);\n });\n\n // The second promise wraps a network idle timeout. This uses playwright's built-in functionality to detect when the network\n // is idle.\n const networkIdlePromise = page.waitForLoadState('networkidle').finally(() => {\n clearTimeout(globalNetworkTimerId);\n });\n\n await Promise.race([globalNetworkTimeout, networkIdlePromise]);\n};\n\nexport const createResourceArchive = async ({\n page,\n networkTimeout,\n assetDomains,\n httpCredentials,\n}: {\n page: Page;\n networkTimeout?: number;\n assetDomains?: string[];\n httpCredentials?: HttpCredentials;\n}): Promise<() => Promise<ResourceArchive>> => {\n const cdpClient = await page.context().newCDPSession(page);\n\n const resourceArchiver = new ResourceArchiver(cdpClient, assetDomains, httpCredentials);\n await resourceArchiver.watch();\n\n return async () => {\n await idle(page, networkTimeout ?? DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS);\n\n return resourceArchiver.archive;\n };\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/makeTest.ts","../src/takeSnapshot.ts","../src/createResourceArchive.ts"],"names":["test","base","expect","join","readFileSync","dedent","rrweb","require","resolve","chromaticSnapshots","Map","takeSnapshot","page","nameOrTestInfo","maybeTestInfo","name","testId","Error","has","get","size","number","on","msg","logger","log","text","domSnapshot","evaluate","bufferedSnapshot","Buffer","from","JSON","stringify","set","idle","__name","networkTimeoutMs","DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS","globalNetworkTimerId","globalNetworkResolver","globalNetworkTimeout","Promise","setTimeout","warn","networkIdlePromise","waitForLoadState","finally","clearTimeout","race","createResourceArchive","networkTimeout","assetDomains","httpCredentials","cdpClient","context","newCDPSession","resourceArchiver","ResourceArchiver","watch","archive","performChromaticSnapshot","delay","diffIncludeAntiAliasing","diffThreshold","disableAutoSnapshot","forcedColors","pauseAnimationAtEnd","prefersReducedMotion","resourceArchiveTimeout","cropToViewport","ignoreSelectors","use","testInfo","project","trackRun","browser","browserType","completeArchive","resourceArchive","snapshots","chromaticStorybookParams","outputDir","writeTestResult","pageUrl","url","viewport","viewportSize","Object","fromEntries","trackComplete","delete","makeTest","extend","undefined","option","chromaticSnapshot","auto"],"mappings":"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","sourcesContent":["import { test as base, expect } from '@playwright/test';\n\nimport { makeTest } from './makeTest';\n\nexport const test = makeTest(base);\nexport { expect };\n\nexport { takeSnapshot } from './takeSnapshot';\nexport type { ChromaticConfig } from '@chromatic-com/shared-e2e';\n","import type {\n TestType,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestInfo,\n Page,\n} from '@playwright/test';\nimport type { ChromaticConfig } from '@chromatic-com/shared-e2e';\nimport {\n writeTestResult,\n trackComplete,\n trackRun,\n DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS,\n} from '@chromatic-com/shared-e2e';\nimport { join } from 'node:path';\nimport { chromaticSnapshots, takeSnapshot } from './takeSnapshot';\nimport { createResourceArchive } from './createResourceArchive';\n\nexport const performChromaticSnapshot = async (\n {\n page,\n delay,\n diffIncludeAntiAliasing,\n diffThreshold,\n disableAutoSnapshot,\n forcedColors,\n pauseAnimationAtEnd,\n prefersReducedMotion,\n resourceArchiveTimeout,\n assetDomains,\n cropToViewport,\n ignoreSelectors,\n }: ChromaticConfig & { page: Page },\n use: () => Promise<void>,\n testInfo: TestInfo\n) => {\n const { testId, project } = testInfo;\n const httpCredentials = project?.use?.httpCredentials;\n\n try {\n trackRun();\n\n // CDP only works in Chromium, so we only capture archives in Chromium.\n // We can later snapshot them in different browsers in the cloud.\n // TODO: I'm not sure if this is the best way to detect the browser version, but\n // it seems to work\n if (page.context().browser().browserType().name() !== 'chromium') {\n await use();\n return;\n }\n\n const completeArchive = await createResourceArchive({\n page,\n networkTimeout: resourceArchiveTimeout,\n assetDomains,\n httpCredentials,\n });\n await use();\n\n if (!disableAutoSnapshot) {\n await takeSnapshot(page, testInfo);\n }\n\n const resourceArchive = await completeArchive();\n const snapshots: Map<string, Buffer> = chromaticSnapshots.get(testId) || new Map();\n\n const chromaticStorybookParams = {\n ...(delay && { delay }),\n ...(diffIncludeAntiAliasing && { diffIncludeAntiAliasing }),\n ...(diffThreshold && { diffThreshold }),\n ...(forcedColors && { forcedColors }),\n ...(pauseAnimationAtEnd && { pauseAnimationAtEnd }),\n ...(prefersReducedMotion && { prefersReducedMotion }),\n ...(cropToViewport && { cropToViewport }),\n ...(ignoreSelectors && { ignoreSelectors }),\n };\n\n // TestInfo.outputDir gives us the test-specific subfolder (https://playwright.dev/docs/api/class-testconfig#test-config-output-dir);\n // we want to write one level above that\n const outputDir = join(testInfo.outputDir, '..');\n await writeTestResult(\n { ...testInfo, outputDir, pageUrl: page.url(), viewport: page.viewportSize() },\n Object.fromEntries(snapshots),\n resourceArchive,\n chromaticStorybookParams\n );\n\n trackComplete();\n } finally {\n // make sure we clear the value associated with this test ID, so the shared chromaticSnapshots object stays small\n chromaticSnapshots.delete(testId);\n }\n};\n\n// We do this slightly odd thing (makeTest) to avoid importing playwright multiple times when\n// linking this package. To avoid the main entry, you can:\n//\n// import { makeTest } from '@chromaui/test-archiver/src/playwright-api/makeTest';\n// import { takeSnapshot as snapshot } from '@chromaui/test-archiver/src/playwright-api/takeSnapshot';\nexport const makeTest = (\n base: TestType<\n PlaywrightTestArgs & PlaywrightTestOptions,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n >\n) =>\n base.extend<ChromaticConfig & { chromaticSnapshot: void }>({\n // ChromaticConfig defaults\n delay: [undefined, { option: true }],\n diffIncludeAntiAliasing: [undefined, { option: true }],\n diffThreshold: [undefined, { option: true }],\n disableAutoSnapshot: [false, { option: true }],\n forcedColors: [undefined, { option: true }],\n pauseAnimationAtEnd: [undefined, { option: true }],\n prefersReducedMotion: [undefined, { option: true }],\n resourceArchiveTimeout: [DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS, { option: true }],\n assetDomains: [[], { option: true }],\n cropToViewport: [undefined, { option: true }],\n ignoreSelectors: [undefined, { option: true }],\n\n chromaticSnapshot: [\n performChromaticSnapshot,\n // ensures this fixture runs without having to be explicitly called (https://playwright.dev/docs/test-fixtures#automatic-fixtures)\n { auto: true },\n ],\n });\n","import type { Page, TestInfo } from '@playwright/test';\nimport { readFileSync } from 'fs';\nimport { dedent } from 'ts-dedent';\nimport type { serializedNodeWithId } from '@chromaui/rrweb-snapshot';\nimport { logger } from '@chromatic-com/shared-e2e';\n\nconst rrweb = readFileSync(require.resolve('@chromaui/rrweb-snapshot'), 'utf8');\n\n// top-level key is the test ID, next level key is the name of the snapshot (which we expect to be unique)\nexport const chromaticSnapshots: Map<string, Map<string, Buffer>> = new Map();\n\nasync function takeSnapshot(page: Page, testInfo: TestInfo): Promise<void>;\nasync function takeSnapshot(page: Page, name: string, testInfo: TestInfo): Promise<void>;\nasync function takeSnapshot(\n page: Page,\n nameOrTestInfo: string | TestInfo,\n maybeTestInfo?: TestInfo\n): Promise<void> {\n let name: string;\n let testId: string;\n if (typeof nameOrTestInfo === 'string') {\n if (!maybeTestInfo) throw new Error('Incorrect usage');\n testId = maybeTestInfo.testId;\n name = nameOrTestInfo;\n } else {\n testId = nameOrTestInfo.testId;\n const number = chromaticSnapshots.has(testId) ? chromaticSnapshots.get(testId).size + 1 : 1;\n name = `Snapshot #${number}`;\n }\n\n page.on('console', (msg) => {\n logger.log(`CONSOLE: \"${msg.text()}\"`);\n });\n\n // Serialize and capture the DOM\n const domSnapshot: serializedNodeWithId = await page.evaluate(dedent`\n ${rrweb};\n\n // this code was erroring the page.evaluate() when it was passed as a function to page.evaluate(),\n // so for now it is being passed as a string until that can be resolved.\n const doPostProcessing = (rrwebSnapshotInstance, documentToSnapshot) => {\n return new Promise((resolve) => {\n const domSnapshot = rrwebSnapshotInstance.snapshot(documentToSnapshot, { recordCanvas: true });\n // do some post-processing on the snapshot\n const toDataURL = async (url) => {\n // read contents of the blob URL\n const response = await fetch(url);\n const blob = await response.blob();\n return new Promise((resolveFileRead, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolveFileRead(reader.result);\n reader.onerror = reject;\n // convert the blob to base64 string\n reader.readAsDataURL(blob);\n });\n };\n\n const replaceBlobUrls = async (node) => {\n await Promise.all(\n node.childNodes.map(async (childNode) => {\n if (childNode.tagName === 'img' && childNode.attributes.src?.startsWith('blob:')) {\n const base64Url = await toDataURL(childNode.attributes.src);\n // eslint-disable-next-line no-param-reassign\n childNode.attributes.src = base64Url;\n }\n\n if (childNode.childNodes?.length) {\n await replaceBlobUrls(childNode);\n }\n })\n );\n };\n\n replaceBlobUrls(domSnapshot).then(() => {\n resolve(domSnapshot);\n });\n });\n };\n\n // page.evaluate returns the value of the function being evaluated. In this case, it means that\n // it is returning either the resolved value of the Promise or the return value of the call to\n // the snapshot function. See https://playwright.dev/docs/api/class-page#page-evaluate.\n if (typeof define === 'function' && define.amd) {\n // AMD support is detected, so we need to load rrwebSnapshot asynchronously\n new Promise((resolve) => {\n // eslint-disable-next-line import/no-dynamic-require, global-require\n require(['rrwebSnapshot'], (rrwebSnapshot) => {\n doPostProcessing(rrwebSnapshot, document).then((domSnapshot) => {\n resolve(domSnapshot);\n });\n });\n });\n } else {\n new Promise((resolve) => {\n doPostProcessing(rrwebSnapshot, document).then((domSnapshot) => {\n resolve(domSnapshot);\n }); \n });\n } \n `);\n\n const bufferedSnapshot = Buffer.from(JSON.stringify(domSnapshot));\n if (!chromaticSnapshots.has(testId)) {\n // map used so the snapshots are always in order\n chromaticSnapshots.set(testId, new Map());\n }\n chromaticSnapshots.get(testId).set(name, bufferedSnapshot);\n}\n\nexport { takeSnapshot };\n","import type { Page } from 'playwright';\nimport {\n ResourceArchiver,\n ResourceArchive,\n DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS,\n logger,\n HttpCredentials,\n} from '@chromatic-com/shared-e2e';\n\nconst idle = async (page: Page, networkTimeoutMs = DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS) => {\n let globalNetworkTimerId: null | ReturnType<typeof setTimeout> = null;\n let globalNetworkResolver: null | (() => void) = null;\n // XXX_jwir3: The way this works is as follows:\n // There are two promises created here. They wrap two separate timers, and we await on a race of both Promises.\n\n // The first promise wraps a global timeout, where all requests MUST complete before that timeout has passed.\n // If the timeout passes, an error is thrown. This promise can only throw errors, it cannot resolve successfully.\n const globalNetworkTimeout = new Promise<void>((resolve) => {\n globalNetworkResolver = resolve;\n\n globalNetworkTimerId = setTimeout(() => {\n logger.warn(`Global timeout of ${networkTimeoutMs}ms reached`);\n globalNetworkResolver();\n }, networkTimeoutMs);\n });\n\n // The second promise wraps a network idle timeout. This uses playwright's built-in functionality to detect when the network\n // is idle.\n const networkIdlePromise = page.waitForLoadState('networkidle').finally(() => {\n clearTimeout(globalNetworkTimerId);\n });\n\n await Promise.race([globalNetworkTimeout, networkIdlePromise]);\n};\n\nexport const createResourceArchive = async ({\n page,\n networkTimeout,\n assetDomains,\n httpCredentials,\n}: {\n page: Page;\n networkTimeout?: number;\n assetDomains?: string[];\n httpCredentials?: HttpCredentials;\n}): Promise<() => Promise<ResourceArchive>> => {\n const cdpClient = await page.context().newCDPSession(page);\n\n const resourceArchiver = new ResourceArchiver(cdpClient, assetDomains, httpCredentials);\n await resourceArchiver.watch();\n\n return async () => {\n await idle(page, networkTimeout ?? DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS);\n\n return resourceArchiver.archive;\n };\n};\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -17,7 +17,7 @@ var da=Object.defineProperty;var s=(t,a)=>da(t,"name",{value:a,configurable:!0})
|
|
|
17
17
|
// so for now it is being passed as a string until that can be resolved.
|
|
18
18
|
const doPostProcessing = (rrwebSnapshotInstance, documentToSnapshot) => {
|
|
19
19
|
return new Promise((resolve) => {
|
|
20
|
-
const domSnapshot = rrwebSnapshotInstance.snapshot(documentToSnapshot);
|
|
20
|
+
const domSnapshot = rrwebSnapshotInstance.snapshot(documentToSnapshot, { recordCanvas: true });
|
|
21
21
|
// do some post-processing on the snapshot
|
|
22
22
|
const toDataURL = async (url) => {
|
|
23
23
|
// read contents of the blob URL
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/makeTest.ts","../src/takeSnapshot.ts","../src/createResourceArchive.ts"],"names":["test","base","expect","join","readFileSync","dedent","rrweb","require","resolve","chromaticSnapshots","Map","takeSnapshot","page","nameOrTestInfo","maybeTestInfo","name","testId","Error","has","get","size","number","on","msg","logger","log","text","domSnapshot","evaluate","bufferedSnapshot","Buffer","from","JSON","stringify","set","idle","__name","networkTimeoutMs","DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS","globalNetworkTimerId","globalNetworkResolver","globalNetworkTimeout","Promise","setTimeout","warn","networkIdlePromise","waitForLoadState","finally","clearTimeout","race","createResourceArchive","networkTimeout","assetDomains","httpCredentials","cdpClient","context","newCDPSession","resourceArchiver","ResourceArchiver","watch","archive","performChromaticSnapshot","delay","diffIncludeAntiAliasing","diffThreshold","disableAutoSnapshot","forcedColors","pauseAnimationAtEnd","prefersReducedMotion","resourceArchiveTimeout","cropToViewport","ignoreSelectors","use","testInfo","project","trackRun","browser","browserType","completeArchive","resourceArchive","snapshots","chromaticStorybookParams","outputDir","writeTestResult","pageUrl","url","viewport","viewportSize","Object","fromEntries","trackComplete","delete","makeTest","extend","undefined","option","chromaticSnapshot","auto"],"mappings":"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","sourcesContent":["import { test as base, expect } from '@playwright/test';\n\nimport { makeTest } from './makeTest';\n\nexport const test = makeTest(base);\nexport { expect };\n\nexport { takeSnapshot } from './takeSnapshot';\nexport type { ChromaticConfig } from '@chromatic-com/shared-e2e';\n","import type {\n TestType,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestInfo,\n Page,\n} from '@playwright/test';\nimport type { ChromaticConfig } from '@chromatic-com/shared-e2e';\nimport {\n writeTestResult,\n trackComplete,\n trackRun,\n DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS,\n} from '@chromatic-com/shared-e2e';\nimport { join } from 'node:path';\nimport { chromaticSnapshots, takeSnapshot } from './takeSnapshot';\nimport { createResourceArchive } from './createResourceArchive';\n\nexport const performChromaticSnapshot = async (\n {\n page,\n delay,\n diffIncludeAntiAliasing,\n diffThreshold,\n disableAutoSnapshot,\n forcedColors,\n pauseAnimationAtEnd,\n prefersReducedMotion,\n resourceArchiveTimeout,\n assetDomains,\n cropToViewport,\n ignoreSelectors,\n }: ChromaticConfig & { page: Page },\n use: () => Promise<void>,\n testInfo: TestInfo\n) => {\n const { testId, project } = testInfo;\n const httpCredentials = project?.use?.httpCredentials;\n\n try {\n trackRun();\n\n // CDP only works in Chromium, so we only capture archives in Chromium.\n // We can later snapshot them in different browsers in the cloud.\n // TODO: I'm not sure if this is the best way to detect the browser version, but\n // it seems to work\n if (page.context().browser().browserType().name() !== 'chromium') {\n await use();\n return;\n }\n\n const completeArchive = await createResourceArchive({\n page,\n networkTimeout: resourceArchiveTimeout,\n assetDomains,\n httpCredentials,\n });\n await use();\n\n if (!disableAutoSnapshot) {\n await takeSnapshot(page, testInfo);\n }\n\n const resourceArchive = await completeArchive();\n const snapshots: Map<string, Buffer> = chromaticSnapshots.get(testId) || new Map();\n\n const chromaticStorybookParams = {\n ...(delay && { delay }),\n ...(diffIncludeAntiAliasing && { diffIncludeAntiAliasing }),\n ...(diffThreshold && { diffThreshold }),\n ...(forcedColors && { forcedColors }),\n ...(pauseAnimationAtEnd && { pauseAnimationAtEnd }),\n ...(prefersReducedMotion && { prefersReducedMotion }),\n ...(cropToViewport && { cropToViewport }),\n ...(ignoreSelectors && { ignoreSelectors }),\n };\n\n // TestInfo.outputDir gives us the test-specific subfolder (https://playwright.dev/docs/api/class-testconfig#test-config-output-dir);\n // we want to write one level above that\n const outputDir = join(testInfo.outputDir, '..');\n await writeTestResult(\n { ...testInfo, outputDir, pageUrl: page.url(), viewport: page.viewportSize() },\n Object.fromEntries(snapshots),\n resourceArchive,\n chromaticStorybookParams\n );\n\n trackComplete();\n } finally {\n // make sure we clear the value associated with this test ID, so the shared chromaticSnapshots object stays small\n chromaticSnapshots.delete(testId);\n }\n};\n\n// We do this slightly odd thing (makeTest) to avoid importing playwright multiple times when\n// linking this package. To avoid the main entry, you can:\n//\n// import { makeTest } from '@chromaui/test-archiver/src/playwright-api/makeTest';\n// import { takeSnapshot as snapshot } from '@chromaui/test-archiver/src/playwright-api/takeSnapshot';\nexport const makeTest = (\n base: TestType<\n PlaywrightTestArgs & PlaywrightTestOptions,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n >\n) =>\n base.extend<ChromaticConfig & { chromaticSnapshot: void }>({\n // ChromaticConfig defaults\n delay: [undefined, { option: true }],\n diffIncludeAntiAliasing: [undefined, { option: true }],\n diffThreshold: [undefined, { option: true }],\n disableAutoSnapshot: [false, { option: true }],\n forcedColors: [undefined, { option: true }],\n pauseAnimationAtEnd: [undefined, { option: true }],\n prefersReducedMotion: [undefined, { option: true }],\n resourceArchiveTimeout: [DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS, { option: true }],\n assetDomains: [[], { option: true }],\n cropToViewport: [undefined, { option: true }],\n ignoreSelectors: [undefined, { option: true }],\n\n chromaticSnapshot: [\n performChromaticSnapshot,\n // ensures this fixture runs without having to be explicitly called (https://playwright.dev/docs/test-fixtures#automatic-fixtures)\n { auto: true },\n ],\n });\n","import type { Page, TestInfo } from '@playwright/test';\nimport { readFileSync } from 'fs';\nimport { dedent } from 'ts-dedent';\nimport type { serializedNodeWithId } from '@chromaui/rrweb-snapshot';\nimport { logger } from '@chromatic-com/shared-e2e';\n\nconst rrweb = readFileSync(require.resolve('@chromaui/rrweb-snapshot'), 'utf8');\n\n// top-level key is the test ID, next level key is the name of the snapshot (which we expect to be unique)\nexport const chromaticSnapshots: Map<string, Map<string, Buffer>> = new Map();\n\nasync function takeSnapshot(page: Page, testInfo: TestInfo): Promise<void>;\nasync function takeSnapshot(page: Page, name: string, testInfo: TestInfo): Promise<void>;\nasync function takeSnapshot(\n page: Page,\n nameOrTestInfo: string | TestInfo,\n maybeTestInfo?: TestInfo\n): Promise<void> {\n let name: string;\n let testId: string;\n if (typeof nameOrTestInfo === 'string') {\n if (!maybeTestInfo) throw new Error('Incorrect usage');\n testId = maybeTestInfo.testId;\n name = nameOrTestInfo;\n } else {\n testId = nameOrTestInfo.testId;\n const number = chromaticSnapshots.has(testId) ? chromaticSnapshots.get(testId).size + 1 : 1;\n name = `Snapshot #${number}`;\n }\n\n page.on('console', (msg) => {\n logger.log(`CONSOLE: \"${msg.text()}\"`);\n });\n\n // Serialize and capture the DOM\n const domSnapshot: serializedNodeWithId = await page.evaluate(dedent`\n ${rrweb};\n\n // this code was erroring the page.evaluate() when it was passed as a function to page.evaluate(),\n // so for now it is being passed as a string until that can be resolved.\n const doPostProcessing = (rrwebSnapshotInstance, documentToSnapshot) => {\n return new Promise((resolve) => {\n const domSnapshot = rrwebSnapshotInstance.snapshot(documentToSnapshot);\n // do some post-processing on the snapshot\n const toDataURL = async (url) => {\n // read contents of the blob URL\n const response = await fetch(url);\n const blob = await response.blob();\n return new Promise((resolveFileRead, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolveFileRead(reader.result);\n reader.onerror = reject;\n // convert the blob to base64 string\n reader.readAsDataURL(blob);\n });\n };\n\n const replaceBlobUrls = async (node) => {\n await Promise.all(\n node.childNodes.map(async (childNode) => {\n if (childNode.tagName === 'img' && childNode.attributes.src?.startsWith('blob:')) {\n const base64Url = await toDataURL(childNode.attributes.src);\n // eslint-disable-next-line no-param-reassign\n childNode.attributes.src = base64Url;\n }\n\n if (childNode.childNodes?.length) {\n await replaceBlobUrls(childNode);\n }\n })\n );\n };\n\n replaceBlobUrls(domSnapshot).then(() => {\n resolve(domSnapshot);\n });\n });\n };\n\n // page.evaluate returns the value of the function being evaluated. In this case, it means that\n // it is returning either the resolved value of the Promise or the return value of the call to\n // the snapshot function. See https://playwright.dev/docs/api/class-page#page-evaluate.\n if (typeof define === 'function' && define.amd) {\n // AMD support is detected, so we need to load rrwebSnapshot asynchronously\n new Promise((resolve) => {\n // eslint-disable-next-line import/no-dynamic-require, global-require\n require(['rrwebSnapshot'], (rrwebSnapshot) => {\n doPostProcessing(rrwebSnapshot, document).then((domSnapshot) => {\n resolve(domSnapshot);\n });\n });\n });\n } else {\n new Promise((resolve) => {\n doPostProcessing(rrwebSnapshot, document).then((domSnapshot) => {\n resolve(domSnapshot);\n }); \n });\n } \n `);\n\n const bufferedSnapshot = Buffer.from(JSON.stringify(domSnapshot));\n if (!chromaticSnapshots.has(testId)) {\n // map used so the snapshots are always in order\n chromaticSnapshots.set(testId, new Map());\n }\n chromaticSnapshots.get(testId).set(name, bufferedSnapshot);\n}\n\nexport { takeSnapshot };\n","import type { Page } from 'playwright';\nimport {\n ResourceArchiver,\n ResourceArchive,\n DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS,\n logger,\n HttpCredentials,\n} from '@chromatic-com/shared-e2e';\n\nconst idle = async (page: Page, networkTimeoutMs = DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS) => {\n let globalNetworkTimerId: null | ReturnType<typeof setTimeout> = null;\n let globalNetworkResolver: null | (() => void) = null;\n // XXX_jwir3: The way this works is as follows:\n // There are two promises created here. They wrap two separate timers, and we await on a race of both Promises.\n\n // The first promise wraps a global timeout, where all requests MUST complete before that timeout has passed.\n // If the timeout passes, an error is thrown. This promise can only throw errors, it cannot resolve successfully.\n const globalNetworkTimeout = new Promise<void>((resolve) => {\n globalNetworkResolver = resolve;\n\n globalNetworkTimerId = setTimeout(() => {\n logger.warn(`Global timeout of ${networkTimeoutMs}ms reached`);\n globalNetworkResolver();\n }, networkTimeoutMs);\n });\n\n // The second promise wraps a network idle timeout. This uses playwright's built-in functionality to detect when the network\n // is idle.\n const networkIdlePromise = page.waitForLoadState('networkidle').finally(() => {\n clearTimeout(globalNetworkTimerId);\n });\n\n await Promise.race([globalNetworkTimeout, networkIdlePromise]);\n};\n\nexport const createResourceArchive = async ({\n page,\n networkTimeout,\n assetDomains,\n httpCredentials,\n}: {\n page: Page;\n networkTimeout?: number;\n assetDomains?: string[];\n httpCredentials?: HttpCredentials;\n}): Promise<() => Promise<ResourceArchive>> => {\n const cdpClient = await page.context().newCDPSession(page);\n\n const resourceArchiver = new ResourceArchiver(cdpClient, assetDomains, httpCredentials);\n await resourceArchiver.watch();\n\n return async () => {\n await idle(page, networkTimeout ?? DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS);\n\n return resourceArchiver.archive;\n };\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/makeTest.ts","../src/takeSnapshot.ts","../src/createResourceArchive.ts"],"names":["test","base","expect","join","readFileSync","dedent","rrweb","require","resolve","chromaticSnapshots","Map","takeSnapshot","page","nameOrTestInfo","maybeTestInfo","name","testId","Error","has","get","size","number","on","msg","logger","log","text","domSnapshot","evaluate","bufferedSnapshot","Buffer","from","JSON","stringify","set","idle","__name","networkTimeoutMs","DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS","globalNetworkTimerId","globalNetworkResolver","globalNetworkTimeout","Promise","setTimeout","warn","networkIdlePromise","waitForLoadState","finally","clearTimeout","race","createResourceArchive","networkTimeout","assetDomains","httpCredentials","cdpClient","context","newCDPSession","resourceArchiver","ResourceArchiver","watch","archive","performChromaticSnapshot","delay","diffIncludeAntiAliasing","diffThreshold","disableAutoSnapshot","forcedColors","pauseAnimationAtEnd","prefersReducedMotion","resourceArchiveTimeout","cropToViewport","ignoreSelectors","use","testInfo","project","trackRun","browser","browserType","completeArchive","resourceArchive","snapshots","chromaticStorybookParams","outputDir","writeTestResult","pageUrl","url","viewport","viewportSize","Object","fromEntries","trackComplete","delete","makeTest","extend","undefined","option","chromaticSnapshot","auto"],"mappings":"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","sourcesContent":["import { test as base, expect } from '@playwright/test';\n\nimport { makeTest } from './makeTest';\n\nexport const test = makeTest(base);\nexport { expect };\n\nexport { takeSnapshot } from './takeSnapshot';\nexport type { ChromaticConfig } from '@chromatic-com/shared-e2e';\n","import type {\n TestType,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestInfo,\n Page,\n} from '@playwright/test';\nimport type { ChromaticConfig } from '@chromatic-com/shared-e2e';\nimport {\n writeTestResult,\n trackComplete,\n trackRun,\n DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS,\n} from '@chromatic-com/shared-e2e';\nimport { join } from 'node:path';\nimport { chromaticSnapshots, takeSnapshot } from './takeSnapshot';\nimport { createResourceArchive } from './createResourceArchive';\n\nexport const performChromaticSnapshot = async (\n {\n page,\n delay,\n diffIncludeAntiAliasing,\n diffThreshold,\n disableAutoSnapshot,\n forcedColors,\n pauseAnimationAtEnd,\n prefersReducedMotion,\n resourceArchiveTimeout,\n assetDomains,\n cropToViewport,\n ignoreSelectors,\n }: ChromaticConfig & { page: Page },\n use: () => Promise<void>,\n testInfo: TestInfo\n) => {\n const { testId, project } = testInfo;\n const httpCredentials = project?.use?.httpCredentials;\n\n try {\n trackRun();\n\n // CDP only works in Chromium, so we only capture archives in Chromium.\n // We can later snapshot them in different browsers in the cloud.\n // TODO: I'm not sure if this is the best way to detect the browser version, but\n // it seems to work\n if (page.context().browser().browserType().name() !== 'chromium') {\n await use();\n return;\n }\n\n const completeArchive = await createResourceArchive({\n page,\n networkTimeout: resourceArchiveTimeout,\n assetDomains,\n httpCredentials,\n });\n await use();\n\n if (!disableAutoSnapshot) {\n await takeSnapshot(page, testInfo);\n }\n\n const resourceArchive = await completeArchive();\n const snapshots: Map<string, Buffer> = chromaticSnapshots.get(testId) || new Map();\n\n const chromaticStorybookParams = {\n ...(delay && { delay }),\n ...(diffIncludeAntiAliasing && { diffIncludeAntiAliasing }),\n ...(diffThreshold && { diffThreshold }),\n ...(forcedColors && { forcedColors }),\n ...(pauseAnimationAtEnd && { pauseAnimationAtEnd }),\n ...(prefersReducedMotion && { prefersReducedMotion }),\n ...(cropToViewport && { cropToViewport }),\n ...(ignoreSelectors && { ignoreSelectors }),\n };\n\n // TestInfo.outputDir gives us the test-specific subfolder (https://playwright.dev/docs/api/class-testconfig#test-config-output-dir);\n // we want to write one level above that\n const outputDir = join(testInfo.outputDir, '..');\n await writeTestResult(\n { ...testInfo, outputDir, pageUrl: page.url(), viewport: page.viewportSize() },\n Object.fromEntries(snapshots),\n resourceArchive,\n chromaticStorybookParams\n );\n\n trackComplete();\n } finally {\n // make sure we clear the value associated with this test ID, so the shared chromaticSnapshots object stays small\n chromaticSnapshots.delete(testId);\n }\n};\n\n// We do this slightly odd thing (makeTest) to avoid importing playwright multiple times when\n// linking this package. To avoid the main entry, you can:\n//\n// import { makeTest } from '@chromaui/test-archiver/src/playwright-api/makeTest';\n// import { takeSnapshot as snapshot } from '@chromaui/test-archiver/src/playwright-api/takeSnapshot';\nexport const makeTest = (\n base: TestType<\n PlaywrightTestArgs & PlaywrightTestOptions,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n >\n) =>\n base.extend<ChromaticConfig & { chromaticSnapshot: void }>({\n // ChromaticConfig defaults\n delay: [undefined, { option: true }],\n diffIncludeAntiAliasing: [undefined, { option: true }],\n diffThreshold: [undefined, { option: true }],\n disableAutoSnapshot: [false, { option: true }],\n forcedColors: [undefined, { option: true }],\n pauseAnimationAtEnd: [undefined, { option: true }],\n prefersReducedMotion: [undefined, { option: true }],\n resourceArchiveTimeout: [DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS, { option: true }],\n assetDomains: [[], { option: true }],\n cropToViewport: [undefined, { option: true }],\n ignoreSelectors: [undefined, { option: true }],\n\n chromaticSnapshot: [\n performChromaticSnapshot,\n // ensures this fixture runs without having to be explicitly called (https://playwright.dev/docs/test-fixtures#automatic-fixtures)\n { auto: true },\n ],\n });\n","import type { Page, TestInfo } from '@playwright/test';\nimport { readFileSync } from 'fs';\nimport { dedent } from 'ts-dedent';\nimport type { serializedNodeWithId } from '@chromaui/rrweb-snapshot';\nimport { logger } from '@chromatic-com/shared-e2e';\n\nconst rrweb = readFileSync(require.resolve('@chromaui/rrweb-snapshot'), 'utf8');\n\n// top-level key is the test ID, next level key is the name of the snapshot (which we expect to be unique)\nexport const chromaticSnapshots: Map<string, Map<string, Buffer>> = new Map();\n\nasync function takeSnapshot(page: Page, testInfo: TestInfo): Promise<void>;\nasync function takeSnapshot(page: Page, name: string, testInfo: TestInfo): Promise<void>;\nasync function takeSnapshot(\n page: Page,\n nameOrTestInfo: string | TestInfo,\n maybeTestInfo?: TestInfo\n): Promise<void> {\n let name: string;\n let testId: string;\n if (typeof nameOrTestInfo === 'string') {\n if (!maybeTestInfo) throw new Error('Incorrect usage');\n testId = maybeTestInfo.testId;\n name = nameOrTestInfo;\n } else {\n testId = nameOrTestInfo.testId;\n const number = chromaticSnapshots.has(testId) ? chromaticSnapshots.get(testId).size + 1 : 1;\n name = `Snapshot #${number}`;\n }\n\n page.on('console', (msg) => {\n logger.log(`CONSOLE: \"${msg.text()}\"`);\n });\n\n // Serialize and capture the DOM\n const domSnapshot: serializedNodeWithId = await page.evaluate(dedent`\n ${rrweb};\n\n // this code was erroring the page.evaluate() when it was passed as a function to page.evaluate(),\n // so for now it is being passed as a string until that can be resolved.\n const doPostProcessing = (rrwebSnapshotInstance, documentToSnapshot) => {\n return new Promise((resolve) => {\n const domSnapshot = rrwebSnapshotInstance.snapshot(documentToSnapshot, { recordCanvas: true });\n // do some post-processing on the snapshot\n const toDataURL = async (url) => {\n // read contents of the blob URL\n const response = await fetch(url);\n const blob = await response.blob();\n return new Promise((resolveFileRead, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolveFileRead(reader.result);\n reader.onerror = reject;\n // convert the blob to base64 string\n reader.readAsDataURL(blob);\n });\n };\n\n const replaceBlobUrls = async (node) => {\n await Promise.all(\n node.childNodes.map(async (childNode) => {\n if (childNode.tagName === 'img' && childNode.attributes.src?.startsWith('blob:')) {\n const base64Url = await toDataURL(childNode.attributes.src);\n // eslint-disable-next-line no-param-reassign\n childNode.attributes.src = base64Url;\n }\n\n if (childNode.childNodes?.length) {\n await replaceBlobUrls(childNode);\n }\n })\n );\n };\n\n replaceBlobUrls(domSnapshot).then(() => {\n resolve(domSnapshot);\n });\n });\n };\n\n // page.evaluate returns the value of the function being evaluated. In this case, it means that\n // it is returning either the resolved value of the Promise or the return value of the call to\n // the snapshot function. See https://playwright.dev/docs/api/class-page#page-evaluate.\n if (typeof define === 'function' && define.amd) {\n // AMD support is detected, so we need to load rrwebSnapshot asynchronously\n new Promise((resolve) => {\n // eslint-disable-next-line import/no-dynamic-require, global-require\n require(['rrwebSnapshot'], (rrwebSnapshot) => {\n doPostProcessing(rrwebSnapshot, document).then((domSnapshot) => {\n resolve(domSnapshot);\n });\n });\n });\n } else {\n new Promise((resolve) => {\n doPostProcessing(rrwebSnapshot, document).then((domSnapshot) => {\n resolve(domSnapshot);\n }); \n });\n } \n `);\n\n const bufferedSnapshot = Buffer.from(JSON.stringify(domSnapshot));\n if (!chromaticSnapshots.has(testId)) {\n // map used so the snapshots are always in order\n chromaticSnapshots.set(testId, new Map());\n }\n chromaticSnapshots.get(testId).set(name, bufferedSnapshot);\n}\n\nexport { takeSnapshot };\n","import type { Page } from 'playwright';\nimport {\n ResourceArchiver,\n ResourceArchive,\n DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS,\n logger,\n HttpCredentials,\n} from '@chromatic-com/shared-e2e';\n\nconst idle = async (page: Page, networkTimeoutMs = DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS) => {\n let globalNetworkTimerId: null | ReturnType<typeof setTimeout> = null;\n let globalNetworkResolver: null | (() => void) = null;\n // XXX_jwir3: The way this works is as follows:\n // There are two promises created here. They wrap two separate timers, and we await on a race of both Promises.\n\n // The first promise wraps a global timeout, where all requests MUST complete before that timeout has passed.\n // If the timeout passes, an error is thrown. This promise can only throw errors, it cannot resolve successfully.\n const globalNetworkTimeout = new Promise<void>((resolve) => {\n globalNetworkResolver = resolve;\n\n globalNetworkTimerId = setTimeout(() => {\n logger.warn(`Global timeout of ${networkTimeoutMs}ms reached`);\n globalNetworkResolver();\n }, networkTimeoutMs);\n });\n\n // The second promise wraps a network idle timeout. This uses playwright's built-in functionality to detect when the network\n // is idle.\n const networkIdlePromise = page.waitForLoadState('networkidle').finally(() => {\n clearTimeout(globalNetworkTimerId);\n });\n\n await Promise.race([globalNetworkTimeout, networkIdlePromise]);\n};\n\nexport const createResourceArchive = async ({\n page,\n networkTimeout,\n assetDomains,\n httpCredentials,\n}: {\n page: Page;\n networkTimeout?: number;\n assetDomains?: string[];\n httpCredentials?: HttpCredentials;\n}): Promise<() => Promise<ResourceArchive>> => {\n const cdpClient = await page.context().newCDPSession(page);\n\n const resourceArchiver = new ResourceArchiver(cdpClient, assetDomains, httpCredentials);\n await resourceArchiver.watch();\n\n return async () => {\n await idle(page, networkTimeout ?? DEFAULT_GLOBAL_RESOURCE_ARCHIVE_TIMEOUT_MS);\n\n return resourceArchiver.archive;\n };\n};\n"]}
|