@plan2net/typo3-playwright-toolkit 0.1.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/LICENSE +338 -0
- package/README.md +404 -0
- package/dist/builders/content-builder.d.ts +30 -0
- package/dist/builders/content-builder.d.ts.map +1 -0
- package/dist/builders/content-builder.js +67 -0
- package/dist/builders/content-builder.js.map +1 -0
- package/dist/builders/content-factory.d.ts +10 -0
- package/dist/builders/content-factory.d.ts.map +1 -0
- package/dist/builders/content-factory.js +23 -0
- package/dist/builders/content-factory.js.map +1 -0
- package/dist/builders/core-content.d.ts +127 -0
- package/dist/builders/core-content.d.ts.map +1 -0
- package/dist/builders/core-content.js +223 -0
- package/dist/builders/core-content.js.map +1 -0
- package/dist/builders/fields.d.ts +7 -0
- package/dist/builders/fields.d.ts.map +1 -0
- package/dist/builders/fields.js +20 -0
- package/dist/builders/fields.js.map +1 -0
- package/dist/builders/identifier.d.ts +2 -0
- package/dist/builders/identifier.d.ts.map +1 -0
- package/dist/builders/identifier.js +11 -0
- package/dist/builders/identifier.js.map +1 -0
- package/dist/builders/page-builder.d.ts +40 -0
- package/dist/builders/page-builder.d.ts.map +1 -0
- package/dist/builders/page-builder.js +119 -0
- package/dist/builders/page-builder.js.map +1 -0
- package/dist/builders/request-context.d.ts +23 -0
- package/dist/builders/request-context.d.ts.map +1 -0
- package/dist/builders/request-context.js +32 -0
- package/dist/builders/request-context.js.map +1 -0
- package/dist/checks/accessibility.d.ts +34 -0
- package/dist/checks/accessibility.d.ts.map +1 -0
- package/dist/checks/accessibility.js +90 -0
- package/dist/checks/accessibility.js.map +1 -0
- package/dist/checks/csp.d.ts +39 -0
- package/dist/checks/csp.d.ts.map +1 -0
- package/dist/checks/csp.js +179 -0
- package/dist/checks/csp.js.map +1 -0
- package/dist/checks/screenshot.d.ts +18 -0
- package/dist/checks/screenshot.d.ts.map +1 -0
- package/dist/checks/screenshot.js +125 -0
- package/dist/checks/screenshot.js.map +1 -0
- package/dist/config.d.ts +100 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +68 -0
- package/dist/config.js.map +1 -0
- package/dist/contract.d.ts +9 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +25 -0
- package/dist/contract.js.map +1 -0
- package/dist/global-setup.d.ts +20 -0
- package/dist/global-setup.d.ts.map +1 -0
- package/dist/global-setup.js +103 -0
- package/dist/global-setup.js.map +1 -0
- package/dist/global-teardown.d.ts +34 -0
- package/dist/global-teardown.d.ts.map +1 -0
- package/dist/global-teardown.js +269 -0
- package/dist/global-teardown.js.map +1 -0
- package/dist/http/api-secret.d.ts +11 -0
- package/dist/http/api-secret.d.ts.map +1 -0
- package/dist/http/api-secret.js +27 -0
- package/dist/http/api-secret.js.map +1 -0
- package/dist/http/cleanup-client.d.ts +26 -0
- package/dist/http/cleanup-client.d.ts.map +1 -0
- package/dist/http/cleanup-client.js +91 -0
- package/dist/http/cleanup-client.js.map +1 -0
- package/dist/http/off-site-headers.d.ts +13 -0
- package/dist/http/off-site-headers.d.ts.map +1 -0
- package/dist/http/off-site-headers.js +27 -0
- package/dist/http/off-site-headers.js.map +1 -0
- package/dist/http/prepare-context.d.ts +4 -0
- package/dist/http/prepare-context.d.ts.map +1 -0
- package/dist/http/prepare-context.js +6 -0
- package/dist/http/prepare-context.js.map +1 -0
- package/dist/http/record-edit.d.ts +37 -0
- package/dist/http/record-edit.d.ts.map +1 -0
- package/dist/http/record-edit.js +86 -0
- package/dist/http/record-edit.js.map +1 -0
- package/dist/http/toolkit-request.d.ts +8 -0
- package/dist/http/toolkit-request.d.ts.map +1 -0
- package/dist/http/toolkit-request.js +45 -0
- package/dist/http/toolkit-request.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect/cli.d.ts +3 -0
- package/dist/inspect/cli.d.ts.map +1 -0
- package/dist/inspect/cli.js +36 -0
- package/dist/inspect/cli.js.map +1 -0
- package/dist/inspect/links.d.ts +10 -0
- package/dist/inspect/links.d.ts.map +1 -0
- package/dist/inspect/links.js +51 -0
- package/dist/inspect/links.js.map +1 -0
- package/dist/inspect/token.d.ts +5 -0
- package/dist/inspect/token.d.ts.map +1 -0
- package/dist/inspect/token.js +15 -0
- package/dist/inspect/token.js.map +1 -0
- package/dist/playwright/base-config.d.ts +4 -0
- package/dist/playwright/base-config.d.ts.map +1 -0
- package/dist/playwright/base-config.js +34 -0
- package/dist/playwright/base-config.js.map +1 -0
- package/dist/playwright/index.d.ts +3 -0
- package/dist/playwright/index.d.ts.map +1 -0
- package/dist/playwright/index.js +3 -0
- package/dist/playwright/index.js.map +1 -0
- package/dist/scenario.d.ts +46 -0
- package/dist/scenario.d.ts.map +1 -0
- package/dist/scenario.js +139 -0
- package/dist/scenario.js.map +1 -0
- package/dist/state/attempt-claim.d.ts +8 -0
- package/dist/state/attempt-claim.d.ts.map +1 -0
- package/dist/state/attempt-claim.js +47 -0
- package/dist/state/attempt-claim.js.map +1 -0
- package/dist/state/attempt-registry.d.ts +36 -0
- package/dist/state/attempt-registry.d.ts.map +1 -0
- package/dist/state/attempt-registry.js +68 -0
- package/dist/state/attempt-registry.js.map +1 -0
- package/dist/state/ensure-state.d.ts +42 -0
- package/dist/state/ensure-state.d.ts.map +1 -0
- package/dist/state/ensure-state.js +169 -0
- package/dist/state/ensure-state.js.map +1 -0
- package/dist/state/json-safe.d.ts +9 -0
- package/dist/state/json-safe.d.ts.map +1 -0
- package/dist/state/json-safe.js +18 -0
- package/dist/state/json-safe.js.map +1 -0
- package/dist/state/run-id.d.ts +6 -0
- package/dist/state/run-id.d.ts.map +1 -0
- package/dist/state/run-id.js +28 -0
- package/dist/state/run-id.js.map +1 -0
- package/dist/state/run-namespace.d.ts +33 -0
- package/dist/state/run-namespace.d.ts.map +1 -0
- package/dist/state/run-namespace.js +136 -0
- package/dist/state/run-namespace.js.map +1 -0
- package/dist/state/safe-paths.d.ts +10 -0
- package/dist/state/safe-paths.d.ts.map +1 -0
- package/dist/state/safe-paths.js +56 -0
- package/dist/state/safe-paths.js.map +1 -0
- package/dist/state/scenario-outcome.d.ts +9 -0
- package/dist/state/scenario-outcome.d.ts.map +1 -0
- package/dist/state/scenario-outcome.js +23 -0
- package/dist/state/scenario-outcome.js.map +1 -0
- package/dist/state/scenario-state.d.ts +41 -0
- package/dist/state/scenario-state.d.ts.map +1 -0
- package/dist/state/scenario-state.js +50 -0
- package/dist/state/scenario-state.js.map +1 -0
- package/dist/state/setup-lock.d.ts +22 -0
- package/dist/state/setup-lock.d.ts.map +1 -0
- package/dist/state/setup-lock.js +90 -0
- package/dist/state/setup-lock.js.map +1 -0
- package/dist/types/common.d.ts +17 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +2 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/content-builder.d.ts +16 -0
- package/dist/types/content-builder.d.ts.map +1 -0
- package/dist/types/content-builder.js +2 -0
- package/dist/types/content-builder.js.map +1 -0
- package/dist/types/playwright-extensions.d.ts +8 -0
- package/dist/types/playwright-extensions.d.ts.map +1 -0
- package/dist/types/playwright-extensions.js +2 -0
- package/dist/types/playwright-extensions.js.map +1 -0
- package/package.json +88 -0
- package/tsconfig.base.json +13 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getToolkitConfig } from '../config.js';
|
|
2
|
+
/** TYPO3's own default for `BE/entryPoint`, which 11.5 and 12.4 have no setting for. */
|
|
3
|
+
export const DEFAULT_BACKEND_PATH = '/typo3';
|
|
4
|
+
/**
|
|
5
|
+
* The fixtures put the test ID on the page and context; an explicit one overrides
|
|
6
|
+
* it. An empty ID is refused rather than sent, because the extension reads an
|
|
7
|
+
* empty header as "use the base database".
|
|
8
|
+
*/
|
|
9
|
+
export function ambientTestId(page) {
|
|
10
|
+
return (page.testId ||
|
|
11
|
+
page.context().testId ||
|
|
12
|
+
undefined);
|
|
13
|
+
}
|
|
14
|
+
export function requireTestId(page, explicit) {
|
|
15
|
+
const testId = explicit || ambientTestId(page) || '';
|
|
16
|
+
if (!testId) {
|
|
17
|
+
throw new Error('[typo3-playwright-toolkit] No test ID for this request. Pass one in, or use a page from the ' +
|
|
18
|
+
'toolkit fixtures — without it the request would run against the base database.');
|
|
19
|
+
}
|
|
20
|
+
return testId;
|
|
21
|
+
}
|
|
22
|
+
export function resolveRequestContext(page, explicit = {}) {
|
|
23
|
+
return {
|
|
24
|
+
// Never derived from page.url(): the request carries the API secret, and a
|
|
25
|
+
// page that navigated off-site must not decide where the builder posts it.
|
|
26
|
+
baseUrl: explicit.baseUrl ?? getToolkitConfig().testingURL,
|
|
27
|
+
backendPath: explicit.backendPath ?? DEFAULT_BACKEND_PATH,
|
|
28
|
+
testId: requireTestId(page, explicit.testId),
|
|
29
|
+
routeToken: explicit.routeToken ?? '',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=request-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../src/builders/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAG/C,wFAAwF;AACxF,MAAM,CAAC,MAAM,oBAAoB,GAAG,QAAQ,CAAA;AAgB5C;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAA0B;IACpD,OAAO,CACF,IAAkC,CAAC,MAAM;QACzC,IAAI,CAAC,OAAO,EAAwB,CAAC,MAAM;QAC5C,SAAS,CACZ,CAAA;AACL,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAA0B,EAAE,QAAiB;IACvE,MAAM,MAAM,GAAG,QAAQ,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IAEpD,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACX,8FAA8F;YAC1F,gFAAgF,CACvF,CAAA;IACL,CAAC;IAED,OAAO,MAAM,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,qBAAqB,CACjC,IAA0B,EAC1B,WAAoC,EAAE;IAEtC,OAAO;QACH,2EAA2E;QAC3E,2EAA2E;QAC3E,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,gBAAgB,EAAE,CAAC,UAAU;QAC1D,WAAW,EAAE,QAAQ,CAAC,WAAW,IAAI,oBAAoB;QACzD,MAAM,EAAE,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC;QAC5C,UAAU,EAAE,QAAQ,CAAC,UAAU,IAAI,EAAE;KACxC,CAAA;AACL,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Page } from '@playwright/test';
|
|
2
|
+
import type { AxeResults } from 'axe-core';
|
|
3
|
+
import { type ToolkitAccessibilityConfig } from '../config.js';
|
|
4
|
+
export declare const DEFAULT_SCAN_TAGS: string[];
|
|
5
|
+
export interface AccessibilityScanOptions {
|
|
6
|
+
disabledRules?: string[];
|
|
7
|
+
include?: string;
|
|
8
|
+
exclude?: string;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
/** Normally taken from test.info(); injectable so this is testable. */
|
|
11
|
+
projectName?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AxeRunInput {
|
|
14
|
+
include: string | null;
|
|
15
|
+
exclude: string | null;
|
|
16
|
+
disabledRules: string[];
|
|
17
|
+
tags: string[];
|
|
18
|
+
}
|
|
19
|
+
export declare function axeSource(): string;
|
|
20
|
+
export declare function shouldScanAutomatically(outcome: {
|
|
21
|
+
status: string;
|
|
22
|
+
navigated: boolean;
|
|
23
|
+
}, config?: {
|
|
24
|
+
auto?: boolean;
|
|
25
|
+
}): boolean;
|
|
26
|
+
export declare function shouldScanProject(projectName: string, projects: string[] | undefined): boolean;
|
|
27
|
+
export declare function buildAxeRunInput(options: AccessibilityScanOptions, config: ToolkitAccessibilityConfig | undefined): AxeRunInput;
|
|
28
|
+
/**
|
|
29
|
+
* @returns axe's raw results, or null when this project is not scanned
|
|
30
|
+
*/
|
|
31
|
+
export declare function scanAccessibility(page: Page, options?: AccessibilityScanOptions): Promise<AxeResults | null>;
|
|
32
|
+
/** Use scanAccessibility instead when you need the results themselves. */
|
|
33
|
+
export declare function runAccessibilityScan(page: Page, options?: AccessibilityScanOptions): Promise<void>;
|
|
34
|
+
//# sourceMappingURL=accessibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility.d.ts","sourceRoot":"","sources":["../../src/checks/accessibility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,KAAK,EAAE,UAAU,EAA8B,MAAM,UAAU,CAAA;AAGtE,OAAO,EAAoB,KAAK,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAGhF,eAAO,MAAM,iBAAiB,UAAgE,CAAA;AAE9F,MAAM,WAAW,wBAAwB;IACrC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,IAAI,EAAE,MAAM,EAAE,CAAA;CACjB;AAID,wBAAgB,SAAS,IAAI,MAAM,CAOlC;AAID,wBAAgB,uBAAuB,CACnC,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,EAC/C,MAAM,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5B,OAAO,CAET;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,OAAO,CAE9F;AAED,wBAAgB,gBAAgB,CAC5B,OAAO,EAAE,wBAAwB,EACjC,MAAM,EAAE,0BAA0B,GAAG,SAAS,GAC/C,WAAW,CAgBb;AAcD;;GAEG;AACH,wBAAsB,iBAAiB,CACnC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,wBAA6B,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAuC5B;AAED,0EAA0E;AAC1E,wBAAsB,oBAAoB,CACtC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,wBAA6B,GACvC,OAAO,CAAC,IAAI,CAAC,CAef"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { getToolkitConfig } from '../config.js';
|
|
5
|
+
// Not AAA: it includes rules most sites intentionally do not meet.
|
|
6
|
+
export const DEFAULT_SCAN_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice'];
|
|
7
|
+
let cachedAxeSource;
|
|
8
|
+
export function axeSource() {
|
|
9
|
+
if (undefined === cachedAxeSource) {
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
cachedAxeSource = fs.readFileSync(require.resolve('axe-core/axe.min.js'), 'utf-8');
|
|
12
|
+
}
|
|
13
|
+
return cachedAxeSource;
|
|
14
|
+
}
|
|
15
|
+
// No configured list means every project: scanning nowhere would leave the suite
|
|
16
|
+
// green while proving nothing.
|
|
17
|
+
export function shouldScanAutomatically(outcome, config) {
|
|
18
|
+
return true === config?.auto && outcome.navigated && 'failed' !== outcome.status;
|
|
19
|
+
}
|
|
20
|
+
export function shouldScanProject(projectName, projects) {
|
|
21
|
+
return !projects || projects.length === 0 || projects.includes(projectName);
|
|
22
|
+
}
|
|
23
|
+
export function buildAxeRunInput(options, config) {
|
|
24
|
+
const disabled = new Set([...(config?.disabledRules ?? []), ...(options.disabledRules ?? [])]);
|
|
25
|
+
// axe checks the headings of one part against the whole page, so a component
|
|
26
|
+
// that starts at h3 fails because of the page's h1. A scan of the whole page
|
|
27
|
+
// still checks the heading order.
|
|
28
|
+
if (options.include) {
|
|
29
|
+
disabled.add('heading-order');
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
include: options.include ?? null,
|
|
33
|
+
exclude: options.exclude ?? null,
|
|
34
|
+
disabledRules: [...disabled],
|
|
35
|
+
tags: options.tags ?? config?.tags ?? DEFAULT_SCAN_TAGS,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function currentProjectName(options) {
|
|
39
|
+
if (options.projectName) {
|
|
40
|
+
return options.projectName;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return test.info().project.name;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @returns axe's raw results, or null when this project is not scanned
|
|
51
|
+
*/
|
|
52
|
+
export async function scanAccessibility(page, options = {}) {
|
|
53
|
+
const config = getToolkitConfig().accessibility;
|
|
54
|
+
const projectName = currentProjectName(options);
|
|
55
|
+
if (!shouldScanProject(projectName, config?.projects)) {
|
|
56
|
+
// Said out loud: a project renamed in playwright.config.ts but not in the
|
|
57
|
+
// toolkit config would otherwise leave every a11y assertion passing on
|
|
58
|
+
// nothing.
|
|
59
|
+
console.warn(`[typo3-playwright-toolkit] No accessibility scan for project "${projectName}": ` +
|
|
60
|
+
`accessibility.projects is [${(config?.projects ?? []).join(', ')}].`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const hasAxe = await page.evaluate(() => 'object' === typeof window.axe);
|
|
64
|
+
if (!hasAxe) {
|
|
65
|
+
await page.evaluate(axeSource());
|
|
66
|
+
}
|
|
67
|
+
// window.axe.run() directly rather than @axe-core/playwright's analyze():
|
|
68
|
+
// ~100-300ms against ~1s, which it spends on cross-frame orchestration this
|
|
69
|
+
// needs. The trade-off is no cross-origin iframe traversal.
|
|
70
|
+
return (await page.evaluate(({ include, exclude, disabledRules, tags }) => {
|
|
71
|
+
const axe = window.axe;
|
|
72
|
+
const context = (include || exclude ? { ...(include && { include }), ...(exclude && { exclude }) } : document);
|
|
73
|
+
return axe.run(context, {
|
|
74
|
+
runOnly: { type: 'tag', values: tags },
|
|
75
|
+
rules: Object.fromEntries(disabledRules.map((rule) => [rule, { enabled: false }])),
|
|
76
|
+
});
|
|
77
|
+
}, buildAxeRunInput(options, config)));
|
|
78
|
+
}
|
|
79
|
+
/** Use scanAccessibility instead when you need the results themselves. */
|
|
80
|
+
export async function runAccessibilityScan(page, options = {}) {
|
|
81
|
+
const results = await scanAccessibility(page, options);
|
|
82
|
+
if (null === results) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// An empty or inert region passes vacuously, which is worse than a violation.
|
|
86
|
+
const evaluatedRules = results.violations.length + results.passes.length + results.incomplete.length;
|
|
87
|
+
expect(evaluatedRules, 'accessibility scan evaluated no rules — the scanned region is empty or inert').toBeGreaterThan(0);
|
|
88
|
+
expect(results.violations).toEqual([]);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=accessibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility.js","sourceRoot":"","sources":["../../src/checks/accessibility.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type BrowserContext, type TestInfo } from '@playwright/test';
|
|
2
|
+
export type CspMode = 'any' | 'report-only' | 'enforced';
|
|
3
|
+
export interface CspViolation {
|
|
4
|
+
blockedUri: string;
|
|
5
|
+
columnNumber: number;
|
|
6
|
+
disposition: SecurityPolicyViolationEventDisposition;
|
|
7
|
+
documentUri: string;
|
|
8
|
+
effectiveDirective: string;
|
|
9
|
+
lineNumber: number;
|
|
10
|
+
originalPolicy: string;
|
|
11
|
+
sample: string;
|
|
12
|
+
sourceFile: string;
|
|
13
|
+
violatedDirective: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DocumentPolicy {
|
|
16
|
+
enforced: string | null;
|
|
17
|
+
reportOnly: string | null;
|
|
18
|
+
url: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function isExpectedDocumentOrigin(documentUri: string, expectedOrigin: string): boolean;
|
|
21
|
+
export declare function policyIssues(policies: DocumentPolicy[], requestedDocuments: string[], mode: CspMode, expectedOrigin: string): string[];
|
|
22
|
+
export declare function describeViolations(violations: CspViolation[]): string[];
|
|
23
|
+
export declare class CspVerifier {
|
|
24
|
+
private readonly context;
|
|
25
|
+
private readonly expectedOrigin;
|
|
26
|
+
private readonly mode;
|
|
27
|
+
private readonly documentRequests;
|
|
28
|
+
private readonly documentPolicies;
|
|
29
|
+
private readonly violations;
|
|
30
|
+
private installed;
|
|
31
|
+
constructor(context: BrowserContext, options?: {
|
|
32
|
+
expectedOrigin?: string;
|
|
33
|
+
mode?: CspMode;
|
|
34
|
+
});
|
|
35
|
+
install(): Promise<void>;
|
|
36
|
+
assertNoViolations(testInfo?: TestInfo, label?: string): Promise<void>;
|
|
37
|
+
private isExpectedOrigin;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=csp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../src/checks/csp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAG7E,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,aAAa,GAAG,UAAU,CAAA;AAExD,MAAM,WAAW,YAAY;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,uCAAuC,CAAA;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,GAAG,EAAE,MAAM,CAAA;CACd;AAQD,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAY7F;AAED,wBAAgB,YAAY,CACxB,QAAQ,EAAE,cAAc,EAAE,EAC1B,kBAAkB,EAAE,MAAM,EAAE,EAC5B,IAAI,EAAE,OAAO,EACb,cAAc,EAAE,MAAM,GACvB,MAAM,EAAE,CAmCV;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,CA0BvE;AAED,qBAAa,WAAW;IAShB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAR5B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAe;IAChD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IACxD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,SAAS,CAAQ;gBAGJ,OAAO,EAAE,cAAc,EACxC,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAO;IAQvD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA+DxB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,SAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA8ClF,OAAO,CAAC,gBAAgB;CAO3B"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import { getToolkitConfig } from '../config.js';
|
|
3
|
+
const BINDING_NAME = '__playwrightReportCspViolation';
|
|
4
|
+
function isPresent(header) {
|
|
5
|
+
return !!header?.trim();
|
|
6
|
+
}
|
|
7
|
+
export function isExpectedDocumentOrigin(documentUri, expectedOrigin) {
|
|
8
|
+
if (!documentUri.trim()) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const documentOrigin = new URL(documentUri).origin;
|
|
13
|
+
return 'null' !== documentOrigin && documentOrigin === expectedOrigin;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function policyIssues(policies, requestedDocuments, mode, expectedOrigin) {
|
|
20
|
+
if (0 === policies.length) {
|
|
21
|
+
if (0 === requestedDocuments.length) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
// Documents were asked for but none answered, so nothing was checked.
|
|
25
|
+
return [
|
|
26
|
+
`No successful same-origin HTML document response was observed for ${expectedOrigin}.\n` +
|
|
27
|
+
`Requested documents:\n${requestedDocuments.map((url) => ` - ${url}`).join('\n')}`,
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
return policies.flatMap((policy) => {
|
|
31
|
+
const issues = [];
|
|
32
|
+
if ('report-only' === mode) {
|
|
33
|
+
if (!isPresent(policy.reportOnly)) {
|
|
34
|
+
issues.push(`Missing Content-Security-Policy-Report-Only header: ${policy.url}`);
|
|
35
|
+
}
|
|
36
|
+
if (isPresent(policy.enforced)) {
|
|
37
|
+
issues.push(`Unexpected enforcing Content-Security-Policy header: ${policy.url}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if ('enforced' === mode) {
|
|
41
|
+
// A report-only header next to it is normal: sites enforce one policy
|
|
42
|
+
// while testing a stricter one.
|
|
43
|
+
if (!isPresent(policy.enforced)) {
|
|
44
|
+
issues.push(`Missing enforcing Content-Security-Policy header: ${policy.url}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (!isPresent(policy.enforced) && !isPresent(policy.reportOnly)) {
|
|
48
|
+
issues.push(`Document served with no Content-Security-Policy header: ${policy.url}`);
|
|
49
|
+
}
|
|
50
|
+
return issues;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function describeViolations(violations) {
|
|
54
|
+
const grouped = new Map();
|
|
55
|
+
for (const violation of violations) {
|
|
56
|
+
grouped.set(violation.effectiveDirective, [
|
|
57
|
+
...(grouped.get(violation.effectiveDirective) ?? []),
|
|
58
|
+
violation,
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
return [...grouped.entries()].map(([directive, group]) => {
|
|
62
|
+
const details = group.map((violation) => {
|
|
63
|
+
const source = violation.sourceFile
|
|
64
|
+
? `${violation.sourceFile}:${violation.lineNumber}:${violation.columnNumber}`
|
|
65
|
+
: violation.documentUri;
|
|
66
|
+
const sample = violation.sample ? `\n Sample: ${violation.sample}` : '';
|
|
67
|
+
return (` - Blocked: ${violation.blockedUri || 'unknown'}\n` +
|
|
68
|
+
` Document: ${violation.documentUri}\n` +
|
|
69
|
+
` Source: ${source}\n` +
|
|
70
|
+
` Disposition: ${violation.disposition}${sample}`);
|
|
71
|
+
});
|
|
72
|
+
return `${directive} (${group.length})\n${details.join('\n')}`;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export class CspVerifier {
|
|
76
|
+
context;
|
|
77
|
+
expectedOrigin;
|
|
78
|
+
mode;
|
|
79
|
+
documentRequests = [];
|
|
80
|
+
documentPolicies = [];
|
|
81
|
+
violations = [];
|
|
82
|
+
installed = false;
|
|
83
|
+
constructor(context, options = {}) {
|
|
84
|
+
this.context = context;
|
|
85
|
+
const config = getToolkitConfig();
|
|
86
|
+
this.expectedOrigin =
|
|
87
|
+
options.expectedOrigin ?? config.csp?.expectedOrigin ?? new URL(config.testingURL).origin;
|
|
88
|
+
this.mode = options.mode ?? config.csp?.mode ?? 'any';
|
|
89
|
+
}
|
|
90
|
+
async install() {
|
|
91
|
+
this.installed = true;
|
|
92
|
+
await this.context.exposeBinding(BINDING_NAME, (_source, violation) => {
|
|
93
|
+
if (isExpectedDocumentOrigin(violation.documentUri, this.expectedOrigin)) {
|
|
94
|
+
this.violations.push(violation);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
await this.context.addInitScript((reportBindingName) => {
|
|
98
|
+
document.addEventListener('securitypolicyviolation', (event) => {
|
|
99
|
+
const report = globalThis[reportBindingName];
|
|
100
|
+
if (!report) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
void report({
|
|
104
|
+
blockedUri: event.blockedURI,
|
|
105
|
+
columnNumber: event.columnNumber,
|
|
106
|
+
disposition: event.disposition,
|
|
107
|
+
documentUri: event.documentURI,
|
|
108
|
+
effectiveDirective: event.effectiveDirective,
|
|
109
|
+
lineNumber: event.lineNumber,
|
|
110
|
+
originalPolicy: event.originalPolicy,
|
|
111
|
+
sample: event.sample,
|
|
112
|
+
sourceFile: event.sourceFile,
|
|
113
|
+
violatedDirective: event.violatedDirective,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}, BINDING_NAME);
|
|
117
|
+
this.context.on('request', (request) => {
|
|
118
|
+
if ('document' === request.resourceType() && this.isExpectedOrigin(request.url())) {
|
|
119
|
+
this.documentRequests.push(request.url());
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
this.context.on('response', (response) => {
|
|
123
|
+
const headers = response.headers();
|
|
124
|
+
const contentType = headers['content-type'] ?? '';
|
|
125
|
+
if ('document' !== response.request().resourceType() ||
|
|
126
|
+
response.status() < 200 ||
|
|
127
|
+
response.status() >= 300 ||
|
|
128
|
+
!contentType.toLowerCase().includes('text/html') ||
|
|
129
|
+
!this.isExpectedOrigin(response.url())) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this.documentPolicies.push({
|
|
133
|
+
enforced: headers['content-security-policy'] ?? null,
|
|
134
|
+
reportOnly: headers['content-security-policy-report-only'] ?? null,
|
|
135
|
+
url: response.url(),
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async assertNoViolations(testInfo, label = 'csp-report') {
|
|
140
|
+
// The listeners live in install(). Without them nothing was ever collected,
|
|
141
|
+
// and this would pass for a page it never saw.
|
|
142
|
+
if (!this.installed) {
|
|
143
|
+
throw new Error('[typo3-playwright-toolkit] CspVerifier.assertNoViolations() before install(). ' +
|
|
144
|
+
'Call install() before the page navigates, or it observes nothing.');
|
|
145
|
+
}
|
|
146
|
+
// Messages from the page arrive in order, so asking the page one more
|
|
147
|
+
// question makes sure the earlier violations have already reached us.
|
|
148
|
+
await Promise.all(this.context
|
|
149
|
+
.pages()
|
|
150
|
+
.filter((page) => !page.isClosed())
|
|
151
|
+
.map((page) => page.evaluate(() => undefined)));
|
|
152
|
+
const issues = [
|
|
153
|
+
...policyIssues(this.documentPolicies, this.documentRequests, this.mode, this.expectedOrigin),
|
|
154
|
+
...describeViolations(this.violations),
|
|
155
|
+
];
|
|
156
|
+
if (issues.length > 0 && testInfo) {
|
|
157
|
+
await testInfo.attach(`${label}.json`, {
|
|
158
|
+
body: Buffer.from(JSON.stringify({
|
|
159
|
+
expectedOrigin: this.expectedOrigin,
|
|
160
|
+
mode: this.mode,
|
|
161
|
+
documentRequests: this.documentRequests,
|
|
162
|
+
documentPolicies: this.documentPolicies,
|
|
163
|
+
violations: this.violations,
|
|
164
|
+
}, null, 2)),
|
|
165
|
+
contentType: 'application/json',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
expect(issues, `CSP verification failed:\n\n${issues.join('\n\n')}`).toHaveLength(0);
|
|
169
|
+
}
|
|
170
|
+
isExpectedOrigin(url) {
|
|
171
|
+
try {
|
|
172
|
+
return new URL(url).origin === this.expectedOrigin;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=csp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.js","sourceRoot":"","sources":["../../src/checks/csp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Locator, Page, PageAssertionsToHaveScreenshotOptions } from '@playwright/test';
|
|
2
|
+
export declare function buildHideStyles(selectors: string[]): string;
|
|
3
|
+
/**
|
|
4
|
+
* takeScreenshot already waits; call this when you interact and then assert
|
|
5
|
+
* without one, such as an accessibility scan after opening an accordion.
|
|
6
|
+
*/
|
|
7
|
+
export declare function waitForAnimations(page: Page, selector?: string, timeout?: number): Promise<void>;
|
|
8
|
+
export declare function loadLazyElements(page: Page): Promise<void>;
|
|
9
|
+
export declare function resolveScreenshotTarget(target: Page | Locator, include?: string): {
|
|
10
|
+
shot: Page | Locator;
|
|
11
|
+
wholePage: boolean;
|
|
12
|
+
};
|
|
13
|
+
export interface ScreenshotOptions extends PageAssertionsToHaveScreenshotOptions {
|
|
14
|
+
/** Shoot only this element. */
|
|
15
|
+
include?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function takeScreenshot(target: Page | Locator, name: string, options?: ScreenshotOptions): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=screenshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/checks/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,OAAO,EAAE,IAAI,EAAE,qCAAqC,EAAE,MAAM,kBAAkB,CAAA;AAW/F,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAM3D;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBpG;AAID,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhE;AA6DD,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,IAAI,GAAG,OAAO,EACtB,OAAO,CAAC,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAM9C;AAED,MAAM,WAAW,iBAAkB,SAAQ,qCAAqC;IAC5E,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAsB,cAAc,CAChC,MAAM,EAAE,IAAI,GAAG,OAAO,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,iBAAsB,GAChC,OAAO,CAAC,IAAI,CAAC,CA6Bf"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import { getToolkitConfig } from '../config.js';
|
|
3
|
+
const FREEZE_STYLES = `* {
|
|
4
|
+
animation-duration: 0s !important;
|
|
5
|
+
transition-duration: 0s !important;
|
|
6
|
+
transition-delay: 0s !important;
|
|
7
|
+
contain-intrinsic-size: none !important;
|
|
8
|
+
content-visibility: visible !important;
|
|
9
|
+
}`;
|
|
10
|
+
export function buildHideStyles(selectors) {
|
|
11
|
+
if (selectors.length === 0) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
return `${selectors.join(', ')} { visibility: hidden !important; }`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* takeScreenshot already waits; call this when you interact and then assert
|
|
18
|
+
* without one, such as an accessibility scan after opening an accordion.
|
|
19
|
+
*/
|
|
20
|
+
export async function waitForAnimations(page, selector, timeout = 5000) {
|
|
21
|
+
await page.evaluate(async ({ selector, timeout }) => {
|
|
22
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(resolve, timeout));
|
|
23
|
+
const root = selector ? document.querySelector(selector) : document.body;
|
|
24
|
+
const waitForAll = async () => {
|
|
25
|
+
const animations = root?.getAnimations({ subtree: true }) ?? [];
|
|
26
|
+
if (animations.length > 0) {
|
|
27
|
+
await Promise.race([
|
|
28
|
+
Promise.all(animations.map((animation) => animation.finished.catch(() => { }))),
|
|
29
|
+
timeoutPromise,
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
// Twice: finishing the first round can start the animations it was chaining.
|
|
34
|
+
await waitForAll();
|
|
35
|
+
await waitForAll();
|
|
36
|
+
}, { selector, timeout });
|
|
37
|
+
}
|
|
38
|
+
// fullPage captures past the viewport instead of scrolling to it, so a lazy
|
|
39
|
+
// element below the fold never starts loading at all.
|
|
40
|
+
export async function loadLazyElements(page) {
|
|
41
|
+
await page.evaluate(() => document
|
|
42
|
+
.querySelectorAll('[loading="lazy"]')
|
|
43
|
+
.forEach((element) => (element.loading = 'eager')));
|
|
44
|
+
}
|
|
45
|
+
async function waitForImagesDecoded(page, timeout = 15000) {
|
|
46
|
+
await page.evaluate(async (timeout) => {
|
|
47
|
+
const images = Array.from(document.querySelectorAll('img'));
|
|
48
|
+
const settle = (image) => new Promise((resolve) => {
|
|
49
|
+
const finish = async () => {
|
|
50
|
+
await image.decode().catch(() => { });
|
|
51
|
+
resolve();
|
|
52
|
+
};
|
|
53
|
+
if (image.complete) {
|
|
54
|
+
void finish();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
image.addEventListener('load', () => void finish(), { once: true });
|
|
58
|
+
image.addEventListener('error', () => resolve(), { once: true });
|
|
59
|
+
});
|
|
60
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(resolve, timeout));
|
|
61
|
+
await Promise.race([Promise.all(images.map(settle)), timeoutPromise]);
|
|
62
|
+
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
|
63
|
+
}, timeout);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Finishes the `media="print"` async-CSS trick by hand. A stylesheet loaded as
|
|
67
|
+
* print and swapped to `all` on load is not applied while it is still print, so
|
|
68
|
+
* a screenshot taken mid-swap captures the page unstyled.
|
|
69
|
+
*/
|
|
70
|
+
async function applyDeferredStylesheets(page) {
|
|
71
|
+
await page.evaluate(() => new Promise((resolve) => {
|
|
72
|
+
const twoFrames = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
73
|
+
const links = Array.from(document.querySelectorAll('link[media="print"]'));
|
|
74
|
+
if (links.length === 0) {
|
|
75
|
+
twoFrames();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const settled = links.map((link) => new Promise((done) => {
|
|
79
|
+
if (link.sheet) {
|
|
80
|
+
link.media = 'all';
|
|
81
|
+
done();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
link.addEventListener('load', () => done(), { once: true });
|
|
85
|
+
link.addEventListener('error', () => done(), { once: true });
|
|
86
|
+
link.media = 'all';
|
|
87
|
+
}));
|
|
88
|
+
// Resolving on rejection too: a stylesheet that never settles must
|
|
89
|
+
// cost a frame, not the whole screenshot.
|
|
90
|
+
Promise.all(settled).then(twoFrames, resolve);
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
export function resolveScreenshotTarget(target, include) {
|
|
94
|
+
if (undefined !== include) {
|
|
95
|
+
return { shot: target.locator(include), wholePage: false };
|
|
96
|
+
}
|
|
97
|
+
return { shot: target, wholePage: !('page' in target) };
|
|
98
|
+
}
|
|
99
|
+
export async function takeScreenshot(target, name, options = {}) {
|
|
100
|
+
const config = getToolkitConfig();
|
|
101
|
+
const { include, ...screenshotOptions } = options;
|
|
102
|
+
const { shot, wholePage } = resolveScreenshotTarget(target, include);
|
|
103
|
+
const page = 'page' in target ? target.page() : target;
|
|
104
|
+
await page.addStyleTag({ content: FREEZE_STYLES });
|
|
105
|
+
const hideStyles = buildHideStyles(config.hideBeforeScreenshot ?? []);
|
|
106
|
+
if (hideStyles) {
|
|
107
|
+
await page.addStyleTag({ content: hideStyles });
|
|
108
|
+
}
|
|
109
|
+
await applyDeferredStylesheets(page);
|
|
110
|
+
await page.evaluate(() => document.fonts.ready);
|
|
111
|
+
await waitForAnimations(page, undefined, 3000);
|
|
112
|
+
await loadLazyElements(page);
|
|
113
|
+
await waitForImagesDecoded(page);
|
|
114
|
+
// Playwright creates a missing reference itself and has --update-snapshots
|
|
115
|
+
// for the rest; hand-building the snapshot path got the platform suffix
|
|
116
|
+
// wrong off Linux.
|
|
117
|
+
await expect(shot).toHaveScreenshot(`${name}.png`, {
|
|
118
|
+
animations: 'disabled',
|
|
119
|
+
timeout: 15000,
|
|
120
|
+
threshold: config.screenshot?.threshold ?? 0.2,
|
|
121
|
+
...(wholePage ? { fullPage: true } : {}),
|
|
122
|
+
...screenshotOptions,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=screenshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/checks/screenshot.ts"],"names":[],"mappings":"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"}
|