@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.
Files changed (164) hide show
  1. package/LICENSE +338 -0
  2. package/README.md +404 -0
  3. package/dist/builders/content-builder.d.ts +30 -0
  4. package/dist/builders/content-builder.d.ts.map +1 -0
  5. package/dist/builders/content-builder.js +67 -0
  6. package/dist/builders/content-builder.js.map +1 -0
  7. package/dist/builders/content-factory.d.ts +10 -0
  8. package/dist/builders/content-factory.d.ts.map +1 -0
  9. package/dist/builders/content-factory.js +23 -0
  10. package/dist/builders/content-factory.js.map +1 -0
  11. package/dist/builders/core-content.d.ts +127 -0
  12. package/dist/builders/core-content.d.ts.map +1 -0
  13. package/dist/builders/core-content.js +223 -0
  14. package/dist/builders/core-content.js.map +1 -0
  15. package/dist/builders/fields.d.ts +7 -0
  16. package/dist/builders/fields.d.ts.map +1 -0
  17. package/dist/builders/fields.js +20 -0
  18. package/dist/builders/fields.js.map +1 -0
  19. package/dist/builders/identifier.d.ts +2 -0
  20. package/dist/builders/identifier.d.ts.map +1 -0
  21. package/dist/builders/identifier.js +11 -0
  22. package/dist/builders/identifier.js.map +1 -0
  23. package/dist/builders/page-builder.d.ts +40 -0
  24. package/dist/builders/page-builder.d.ts.map +1 -0
  25. package/dist/builders/page-builder.js +119 -0
  26. package/dist/builders/page-builder.js.map +1 -0
  27. package/dist/builders/request-context.d.ts +23 -0
  28. package/dist/builders/request-context.d.ts.map +1 -0
  29. package/dist/builders/request-context.js +32 -0
  30. package/dist/builders/request-context.js.map +1 -0
  31. package/dist/checks/accessibility.d.ts +34 -0
  32. package/dist/checks/accessibility.d.ts.map +1 -0
  33. package/dist/checks/accessibility.js +90 -0
  34. package/dist/checks/accessibility.js.map +1 -0
  35. package/dist/checks/csp.d.ts +39 -0
  36. package/dist/checks/csp.d.ts.map +1 -0
  37. package/dist/checks/csp.js +179 -0
  38. package/dist/checks/csp.js.map +1 -0
  39. package/dist/checks/screenshot.d.ts +18 -0
  40. package/dist/checks/screenshot.d.ts.map +1 -0
  41. package/dist/checks/screenshot.js +125 -0
  42. package/dist/checks/screenshot.js.map +1 -0
  43. package/dist/config.d.ts +100 -0
  44. package/dist/config.d.ts.map +1 -0
  45. package/dist/config.js +68 -0
  46. package/dist/config.js.map +1 -0
  47. package/dist/contract.d.ts +9 -0
  48. package/dist/contract.d.ts.map +1 -0
  49. package/dist/contract.js +25 -0
  50. package/dist/contract.js.map +1 -0
  51. package/dist/global-setup.d.ts +20 -0
  52. package/dist/global-setup.d.ts.map +1 -0
  53. package/dist/global-setup.js +103 -0
  54. package/dist/global-setup.js.map +1 -0
  55. package/dist/global-teardown.d.ts +34 -0
  56. package/dist/global-teardown.d.ts.map +1 -0
  57. package/dist/global-teardown.js +269 -0
  58. package/dist/global-teardown.js.map +1 -0
  59. package/dist/http/api-secret.d.ts +11 -0
  60. package/dist/http/api-secret.d.ts.map +1 -0
  61. package/dist/http/api-secret.js +27 -0
  62. package/dist/http/api-secret.js.map +1 -0
  63. package/dist/http/cleanup-client.d.ts +26 -0
  64. package/dist/http/cleanup-client.d.ts.map +1 -0
  65. package/dist/http/cleanup-client.js +91 -0
  66. package/dist/http/cleanup-client.js.map +1 -0
  67. package/dist/http/off-site-headers.d.ts +13 -0
  68. package/dist/http/off-site-headers.d.ts.map +1 -0
  69. package/dist/http/off-site-headers.js +27 -0
  70. package/dist/http/off-site-headers.js.map +1 -0
  71. package/dist/http/prepare-context.d.ts +4 -0
  72. package/dist/http/prepare-context.d.ts.map +1 -0
  73. package/dist/http/prepare-context.js +6 -0
  74. package/dist/http/prepare-context.js.map +1 -0
  75. package/dist/http/record-edit.d.ts +37 -0
  76. package/dist/http/record-edit.d.ts.map +1 -0
  77. package/dist/http/record-edit.js +86 -0
  78. package/dist/http/record-edit.js.map +1 -0
  79. package/dist/http/toolkit-request.d.ts +8 -0
  80. package/dist/http/toolkit-request.d.ts.map +1 -0
  81. package/dist/http/toolkit-request.js +45 -0
  82. package/dist/http/toolkit-request.js.map +1 -0
  83. package/dist/index.d.ts +18 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +14 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/inspect/cli.d.ts +3 -0
  88. package/dist/inspect/cli.d.ts.map +1 -0
  89. package/dist/inspect/cli.js +36 -0
  90. package/dist/inspect/cli.js.map +1 -0
  91. package/dist/inspect/links.d.ts +10 -0
  92. package/dist/inspect/links.d.ts.map +1 -0
  93. package/dist/inspect/links.js +51 -0
  94. package/dist/inspect/links.js.map +1 -0
  95. package/dist/inspect/token.d.ts +5 -0
  96. package/dist/inspect/token.d.ts.map +1 -0
  97. package/dist/inspect/token.js +15 -0
  98. package/dist/inspect/token.js.map +1 -0
  99. package/dist/playwright/base-config.d.ts +4 -0
  100. package/dist/playwright/base-config.d.ts.map +1 -0
  101. package/dist/playwright/base-config.js +34 -0
  102. package/dist/playwright/base-config.js.map +1 -0
  103. package/dist/playwright/index.d.ts +3 -0
  104. package/dist/playwright/index.d.ts.map +1 -0
  105. package/dist/playwright/index.js +3 -0
  106. package/dist/playwright/index.js.map +1 -0
  107. package/dist/scenario.d.ts +46 -0
  108. package/dist/scenario.d.ts.map +1 -0
  109. package/dist/scenario.js +139 -0
  110. package/dist/scenario.js.map +1 -0
  111. package/dist/state/attempt-claim.d.ts +8 -0
  112. package/dist/state/attempt-claim.d.ts.map +1 -0
  113. package/dist/state/attempt-claim.js +47 -0
  114. package/dist/state/attempt-claim.js.map +1 -0
  115. package/dist/state/attempt-registry.d.ts +36 -0
  116. package/dist/state/attempt-registry.d.ts.map +1 -0
  117. package/dist/state/attempt-registry.js +68 -0
  118. package/dist/state/attempt-registry.js.map +1 -0
  119. package/dist/state/ensure-state.d.ts +42 -0
  120. package/dist/state/ensure-state.d.ts.map +1 -0
  121. package/dist/state/ensure-state.js +169 -0
  122. package/dist/state/ensure-state.js.map +1 -0
  123. package/dist/state/json-safe.d.ts +9 -0
  124. package/dist/state/json-safe.d.ts.map +1 -0
  125. package/dist/state/json-safe.js +18 -0
  126. package/dist/state/json-safe.js.map +1 -0
  127. package/dist/state/run-id.d.ts +6 -0
  128. package/dist/state/run-id.d.ts.map +1 -0
  129. package/dist/state/run-id.js +28 -0
  130. package/dist/state/run-id.js.map +1 -0
  131. package/dist/state/run-namespace.d.ts +33 -0
  132. package/dist/state/run-namespace.d.ts.map +1 -0
  133. package/dist/state/run-namespace.js +136 -0
  134. package/dist/state/run-namespace.js.map +1 -0
  135. package/dist/state/safe-paths.d.ts +10 -0
  136. package/dist/state/safe-paths.d.ts.map +1 -0
  137. package/dist/state/safe-paths.js +56 -0
  138. package/dist/state/safe-paths.js.map +1 -0
  139. package/dist/state/scenario-outcome.d.ts +9 -0
  140. package/dist/state/scenario-outcome.d.ts.map +1 -0
  141. package/dist/state/scenario-outcome.js +23 -0
  142. package/dist/state/scenario-outcome.js.map +1 -0
  143. package/dist/state/scenario-state.d.ts +41 -0
  144. package/dist/state/scenario-state.d.ts.map +1 -0
  145. package/dist/state/scenario-state.js +50 -0
  146. package/dist/state/scenario-state.js.map +1 -0
  147. package/dist/state/setup-lock.d.ts +22 -0
  148. package/dist/state/setup-lock.d.ts.map +1 -0
  149. package/dist/state/setup-lock.js +90 -0
  150. package/dist/state/setup-lock.js.map +1 -0
  151. package/dist/types/common.d.ts +17 -0
  152. package/dist/types/common.d.ts.map +1 -0
  153. package/dist/types/common.js +2 -0
  154. package/dist/types/common.js.map +1 -0
  155. package/dist/types/content-builder.d.ts +16 -0
  156. package/dist/types/content-builder.d.ts.map +1 -0
  157. package/dist/types/content-builder.js +2 -0
  158. package/dist/types/content-builder.js.map +1 -0
  159. package/dist/types/playwright-extensions.d.ts +8 -0
  160. package/dist/types/playwright-extensions.d.ts.map +1 -0
  161. package/dist/types/playwright-extensions.js +2 -0
  162. package/dist/types/playwright-extensions.js.map +1 -0
  163. package/package.json +88 -0
  164. 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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"}