@gooddata/sdk-e2e-utils 11.61.0-alpha.1 → 11.61.0-alpha.2

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 (31) hide show
  1. package/esm/create-test/feature-hub/mock.d.ts +2 -2
  2. package/esm/create-test/feature-hub/mock.d.ts.map +1 -1
  3. package/esm/create-test/goodmock/snapshot/sanitize-organization.d.ts +12 -0
  4. package/esm/create-test/goodmock/snapshot/sanitize-organization.d.ts.map +1 -0
  5. package/esm/create-test/goodmock/snapshot/sanitize-organization.js +28 -0
  6. package/esm/create-test/goodmock/snapshot/snapshot-and-save-recording.d.ts +6 -0
  7. package/esm/create-test/goodmock/snapshot/snapshot-and-save-recording.d.ts.map +1 -1
  8. package/esm/create-test/goodmock/snapshot/snapshot-and-save-recording.js +4 -2
  9. package/esm/create-test/playwright/factory.d.ts +3 -2
  10. package/esm/create-test/playwright/factory.d.ts.map +1 -1
  11. package/esm/create-test/playwright/factory.js +98 -40
  12. package/esm/create-test/playwright/playwright-internals.d.ts +10 -5
  13. package/esm/create-test/playwright/playwright-internals.d.ts.map +1 -1
  14. package/esm/create-test/playwright/playwright-internals.js +12 -8
  15. package/esm/create-test/playwright/playwright-types.d.ts +13 -5
  16. package/esm/create-test/playwright/playwright-types.d.ts.map +1 -1
  17. package/esm/create-test/playwright/test-conditions.d.ts +25 -0
  18. package/esm/create-test/playwright/test-conditions.d.ts.map +1 -0
  19. package/esm/create-test/playwright/test-conditions.js +76 -0
  20. package/esm/create-test/playwright/to-playwright-details.d.ts +2 -2
  21. package/esm/create-test/playwright/to-playwright-details.d.ts.map +1 -1
  22. package/esm/create-test/playwright/to-playwright-details.js +1 -1
  23. package/esm/create-test/playwright/types.d.ts +90 -33
  24. package/esm/create-test/playwright/types.d.ts.map +1 -1
  25. package/esm/create-test/playwright/with-describe-details.d.ts +10 -3
  26. package/esm/create-test/playwright/with-describe-details.d.ts.map +1 -1
  27. package/esm/create-test/playwright/with-describe-details.js +47 -14
  28. package/esm/index.d.ts +2 -2
  29. package/esm/index.d.ts.map +1 -1
  30. package/esm/sdk-e2e-utils.d.ts +119 -47
  31. package/package.json +3 -3
@@ -1,5 +1,5 @@
1
- import type { PlaywrightTest } from "../playwright/playwright-types.js";
1
+ import type { PlaywrightTestInstance } from "../playwright/playwright-types.js";
2
2
  import type { IFeatureHubEnvironment } from "./types.js";
3
3
  /** FeatureHub mock — each test gets a fresh page, so route dies with it. */
4
- export declare function registerFeatureHubMock(testInstance: PlaywrightTest, featureHubResponse: IFeatureHubEnvironment[]): void;
4
+ export declare function registerFeatureHubMock(testInstance: PlaywrightTestInstance, featureHubResponse: IFeatureHubEnvironment[]): void;
5
5
  //# sourceMappingURL=mock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../../src/create-test/feature-hub/mock.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAExE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAClC,YAAY,EAAE,cAAc,EAC5B,kBAAkB,EAAE,sBAAsB,EAAE,GAC7C,IAAI,CAYN"}
1
+ {"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../../src/create-test/feature-hub/mock.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEhF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAClC,YAAY,EAAE,sBAAsB,EACpC,kBAAkB,EAAE,sBAAsB,EAAE,GAC7C,IAAI,CAYN"}
@@ -0,0 +1,12 @@
1
+ import { type IGoodmockMapping } from "../types.js";
2
+ export declare const DEFAULT_TEMPLATE_ORGANIZATION = "automation";
3
+ /**
4
+ * Replace every ephemeral organization name (`ephemeral-{templateOrganization}-{hex}`) with the
5
+ * bare template organization name, so re-recording against a freshly created org does not churn
6
+ * the organization id, name, or `/entities/admin/organizations/...` paths in the saved mappings.
7
+ *
8
+ * The hex suffix is exactly 8 lowercase hex characters; the lookahead makes sure a longer hex run
9
+ * is not partially consumed.
10
+ */
11
+ export declare function sanitizeOrganization(mappings: IGoodmockMapping[], templateOrganization?: string): IGoodmockMapping[];
12
+ //# sourceMappingURL=sanitize-organization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-organization.d.ts","sourceRoot":"","sources":["../../../../src/create-test/goodmock/snapshot/sanitize-organization.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,eAAO,MAAM,6BAA6B,eAAe,CAAC;AAM1D;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,oBAAoB,GAAE,MAAsC,GAC7D,gBAAgB,EAAE,CAmBpB"}
@@ -0,0 +1,28 @@
1
+ // (C) 2026 GoodData Corporation
2
+ export const DEFAULT_TEMPLATE_ORGANIZATION = "automation";
3
+ function escapeRegExp(value) {
4
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ }
6
+ /**
7
+ * Replace every ephemeral organization name (`ephemeral-{templateOrganization}-{hex}`) with the
8
+ * bare template organization name, so re-recording against a freshly created org does not churn
9
+ * the organization id, name, or `/entities/admin/organizations/...` paths in the saved mappings.
10
+ *
11
+ * The hex suffix is exactly 8 lowercase hex characters; the lookahead makes sure a longer hex run
12
+ * is not partially consumed.
13
+ */
14
+ export function sanitizeOrganization(mappings, templateOrganization = DEFAULT_TEMPLATE_ORGANIZATION) {
15
+ const template = escapeRegExp(templateOrganization);
16
+ const ephemeralOrganizationPattern = new RegExp(`ephemeral-${template}-[0-9a-f]{8}(?![0-9a-f])`, "g");
17
+ const dataString = JSON.stringify({ mappings });
18
+ const matches = dataString.match(ephemeralOrganizationPattern);
19
+ if (!matches) {
20
+ return mappings;
21
+ }
22
+ const sanitizedDataString = dataString.replace(ephemeralOrganizationPattern, templateOrganization);
23
+ const sanitizedData = JSON.parse(sanitizedDataString);
24
+ const distinctOrganizations = [...new Set(matches)].join(", ");
25
+ // oxlint-disable-next-line eslint-js/no-console
26
+ console.log(`Sanitized organization in mappings (${distinctOrganizations} -> ${templateOrganization})`);
27
+ return sanitizedData.mappings;
28
+ }
@@ -5,6 +5,12 @@ import { type IGoodmockMapping, type ILeakPattern, type ISecretMapping, type IWo
5
5
  export interface ISnapshotAndSaveRecordingOptions {
6
6
  /** Source/target workspace ID rewrite(s) applied before mappings are saved. */
7
7
  workspaceIdMappings?: IWorkspaceIdMapping | IWorkspaceIdMapping[];
8
+ /**
9
+ * Template organization name. Every ephemeral organization name in the saved mappings
10
+ * (`ephemeral-{templateOrganization}-{hex}`) is replaced with this bare name so re-recording
11
+ * against a fresh ephemeral org does not churn the recordings. Defaults to "automation".
12
+ */
13
+ templateOrganization?: string;
8
14
  /**
9
15
  * Real backend URL (e.g. "https://example.gooddata.com"). When provided together with
10
16
  * baseUrl, all occurrences are replaced in the saved mappings so that recorded responses
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot-and-save-recording.d.ts","sourceRoot":"","sources":["../../../../src/create-test/goodmock/snapshot/snapshot-and-save-recording.ts"],"names":[],"mappings":"AAKA,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EAC3B,MAAM,aAAa,CAAC;AASrB;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAC7C,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,CAAC;IAClE;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gGAAgG;IAChG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,EAAE,CAAC;IAClC;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,CAAC;CAC3E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAC1C,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,EACvB,OAAO,GAAE,gCAAqC,GAC/C,OAAO,CAAC,IAAI,CAAC,CAoDf"}
1
+ {"version":3,"file":"snapshot-and-save-recording.d.ts","sourceRoot":"","sources":["../../../../src/create-test/goodmock/snapshot/snapshot-and-save-recording.ts"],"names":[],"mappings":"AAKA,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EAC3B,MAAM,aAAa,CAAC;AAUrB;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAC7C,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,CAAC;IAClE;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gGAAgG;IAChG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,EAAE,CAAC;IAClC;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,CAAC;CAC3E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAC1C,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,EACvB,OAAO,GAAE,gCAAqC,GAC/C,OAAO,CAAC,IAAI,CAAC,CA6Df"}
@@ -5,6 +5,7 @@ import { assertNoLeakPatterns } from "./assert-no-leak-patterns.js";
5
5
  import { assertNoSecretLeaks } from "./assert-no-secret-leaks.js";
6
6
  import { replaceSecrets } from "./replace-secrets.js";
7
7
  import { sanitizeCredentials } from "./sanitize-credentials.js";
8
+ import { DEFAULT_TEMPLATE_ORGANIZATION, sanitizeOrganization } from "./sanitize-organization.js";
8
9
  import { sanitizeWorkspaceIds } from "./sanitize-workspace-ids.js";
9
10
  import { snapshotParams } from "./snapshot-param.js";
10
11
  /**
@@ -21,7 +22,7 @@ import { snapshotParams } from "./snapshot-param.js";
21
22
  * @param options - See {@link ISnapshotAndSaveRecordingOptions}.
22
23
  */
23
24
  export async function snapshotAndSaveRecording(host, mappingFilePath, options = {}) {
24
- const { workspaceIdMappings, backendHost, baseUrl, secretMappings, leakPatterns, sanitizeMappings } = options;
25
+ const { workspaceIdMappings, templateOrganization = DEFAULT_TEMPLATE_ORGANIZATION, backendHost, baseUrl, secretMappings, leakPatterns, sanitizeMappings, } = options;
25
26
  // Snapshot everything with scenarios enabled. goodmock only emits a scenario
26
27
  // chain when a given request produced *different* responses across the
27
28
  // recording; identical repeats collapse to a single mapping. This captures
@@ -49,7 +50,8 @@ export async function snapshotAndSaveRecording(host, mappingFilePath, options =
49
50
  // Consumer-supplied redaction for secrets this module has no knowledge of (e.g. an LLM
50
51
  // provider's API key echoed back on a settings entity).
51
52
  const sanitizedByCaller = sanitizeMappings ? sanitizeMappings(mappings) : mappings;
52
- const sanitizedMappings = sanitizeWorkspaceIds(sanitizedByCaller, workspaceIdMappings);
53
+ const sanitizedWorkspaceIds = sanitizeWorkspaceIds(sanitizedByCaller, workspaceIdMappings);
54
+ const sanitizedMappings = sanitizeOrganization(sanitizedWorkspaceIds, templateOrganization);
53
55
  let output = JSON.stringify({ mappings: sanitizedMappings }, null, 4) + "\n";
54
56
  if (backendHost && baseUrl) {
55
57
  output = output.replaceAll(backendHost, baseUrl);
@@ -1,7 +1,8 @@
1
- import type { ICreateTestOptions, Test } from "./types.js";
1
+ import type { PlaywrightBaseTestArgs, PlaywrightBaseWorkerArgs } from "./playwright-types.js";
2
+ import type { ICreateTestOptions, Test, TestConditionsValueConstraint } from "./types.js";
2
3
  /**
3
4
  * @internal
4
5
  * @param options -
5
6
  */
6
- export declare function createTest<T extends {} = {}, W extends {} = {}>(options: ICreateTestOptions<T, W>): Test;
7
+ export declare function createTest<TestConditionsValue extends TestConditionsValueConstraint = never, T extends {} = {}, W extends {} = {}>(options: ICreateTestOptions<TestConditionsValue, T, W>): Test<TestConditionsValue, PlaywrightBaseTestArgs & T, PlaywrightBaseWorkerArgs & W>;
7
8
  //# sourceMappingURL=factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/factory.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAER,kBAAkB,EAElB,IAAI,EAGP,MAAM,YAAY,CAAC;AAGpB;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CA8GxG"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/factory.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACR,sBAAsB,EACtB,wBAAwB,EAE3B,MAAM,uBAAuB,CAAC;AAQ/B,OAAO,KAAK,EAER,kBAAkB,EAElB,IAAI,EACJ,6BAA6B,EAGhC,MAAM,YAAY,CAAC;AAGpB;;;GAGG;AACH,wBAAgB,UAAU,CACtB,mBAAmB,SAAS,6BAA6B,GAAG,KAAK,EACjE,CAAC,SAAS,EAAE,GAAG,EAAE,EACjB,CAAC,SAAS,EAAE,GAAG,EAAE,EAEjB,OAAO,EAAE,kBAAkB,CAAC,mBAAmB,EAAE,CAAC,EAAE,CAAC,CAAC,GACvD,IAAI,CAAC,mBAAmB,EAAE,sBAAsB,GAAG,CAAC,EAAE,wBAAwB,GAAG,CAAC,CAAC,CAsMrF"}
@@ -1,68 +1,124 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  // oxlint-disable typescript-eslint/no-empty-object-type
3
+ import { basename } from "path";
3
4
  import { test } from "@playwright/test";
4
5
  import { registerFeatureHubMock } from "../feature-hub/mock.js";
5
6
  import { registerGoodmockHooks } from "../goodmock/playwright.js";
6
7
  import { getCallerLocation, getPlaywrightInternals, } from "./playwright-internals.js";
8
+ import { assertRequiredConditions, assertValidConditions, combineConditions, shouldRunTest, } from "./test-conditions.js";
7
9
  import { toPlaywrightDetails } from "./to-playwright-details.js";
8
- import { withDescribeDetails } from "./with-describe-details.js";
10
+ import { withDescribeDetails, withTestDetails } from "./with-describe-details.js";
9
11
  /**
10
12
  * @internal
11
13
  * @param options -
12
14
  */
13
15
  export function createTest(options) {
14
- // Tracks whether we're inside a topLevelDescribe during synchronous registration.
15
- let insideTopLevelDescribe = false;
16
+ // Tracks whether we're inside the outermost describe of a spec file during synchronous registration.
17
+ let insideRootDescribe = false;
16
18
  // Stacks track workspace settings / additionalWindowProperties hierarchy during
17
19
  // synchronous describe registration. Each level merges on top of the parent's.
18
20
  // The beforeEach at each level captures the fully-merged snapshot, so the
19
21
  // innermost beforeEach (which runs last) wins.
20
22
  const settingsStack = [];
21
23
  const awpStack = [];
22
- const testInstance = options.fixtures ? test.extend(options.fixtures) : test;
24
+ // Combined test conditions and titles of the enclosing describes, during synchronous registration.
25
+ // Describes only feed these; each test decides for itself whether it runs (a describe whose own
26
+ // conditions are false may still contain tests whose combined conditions are true).
27
+ const { testConditions } = options;
28
+ const conditionsStack = [];
29
+ const titleStack = [];
30
+ const withDescribeScope = (title, details, body) => {
31
+ conditionsStack.push(combineConditions(testConditions, conditionsStack[conditionsStack.length - 1], details.conditions));
32
+ titleStack.push(title);
33
+ try {
34
+ body();
35
+ }
36
+ finally {
37
+ titleStack.pop();
38
+ conditionsStack.pop();
39
+ }
40
+ };
41
+ // Same type `test.extend<T, W>()` returns. Without fixtures, T and W (inferred from `options.fixtures`)
42
+ // add nothing, so the base `test` already is that type.
43
+ const testInstance = options.fixtures
44
+ ? test.extend(options.fixtures)
45
+ : test;
23
46
  const internals = getPlaywrightInternals(testInstance);
47
+ // specName -> spec file it was derived from. Playwright loads each file once per process, so the
48
+ // same file again means a second outermost describe in it, and another file means a duplicate specName.
49
+ const specNameFiles = new Map();
24
50
  const makeDescribe = (type, name) => {
25
- return (title, details, callback) => {
26
- if (!insideTopLevelDescribe) {
27
- throw new Error(`${name}("${title}") must be nested inside a test.topLevelDescribe() block.`);
51
+ const describeFunction = (title, details, callback) => {
52
+ const location = getCallerLocation(describeFunction);
53
+ assertValidConditions(details.conditions, `${name}("${title}")`);
54
+ const pwDetails = toPlaywrightDetails(details);
55
+ if (insideRootDescribe) {
56
+ internals.describe(type, location, title, pwDetails, () => withDescribeScope(title, details, () => withDescribeDetails(testInstance, settingsStack, awpStack, details, callback)));
57
+ return;
28
58
  }
29
- internals.describe(type, getCallerLocation(), title, toPlaywrightDetails(details), () => withDescribeDetails(testInstance, settingsStack, awpStack, details, callback));
30
- };
31
- };
32
- // Spec files that already have a topLevelDescribe. Playwright loads each file once per process,
33
- // so a second registration from the same file means a second topLevelDescribe in that file.
34
- const filesWithTopLevelDescribe = new Set();
35
- const makeTopLevelDescribe = (type) => {
36
- return (suiteName, specName, details, fn) => {
37
- const location = getCallerLocation();
38
- if (filesWithTopLevelDescribe.has(location.file)) {
39
- throw new Error(`test.topLevelDescribe("${suiteName}"): only one test.topLevelDescribe() is allowed per file (${location.file}).`);
59
+ // Outermost describe of the spec file: it also sets up the spec.
60
+ // "some-file-name.spec.ts" -> "some-file-name"
61
+ const specName = basename(location.file).replace(/\.(spec|test)\.[cm]?[jt]sx?$/, "");
62
+ const existingFile = specNameFiles.get(specName);
63
+ if (existingFile === location.file) {
64
+ throw new Error(`${name}("${title}"): only one outermost test.describe() is allowed per file (${location.file}).`);
65
+ }
66
+ if (existingFile !== undefined) {
67
+ throw new Error(`${name}("${title}"): duplicate specName "${specName}" in ${location.file} and ${existingFile}. Spec file names must be unique.`);
40
68
  }
41
- filesWithTopLevelDescribe.add(location.file);
42
- const suite = () => {
69
+ specNameFiles.set(specName, location.file);
70
+ internals.describe(type, location, title, pwDetails, () => {
43
71
  registerFeatureHubMock(testInstance, options.featureHubResponse);
44
72
  if (options.goodmock) {
45
73
  registerGoodmockHooks(testInstance, options.goodmock, specName);
46
74
  }
47
75
  // Workspace settings + additional window properties — merge with parent
48
- // stack and inject beforeEach. Wraps fn() so nested describes inherit.
49
- insideTopLevelDescribe = true;
50
- withDescribeDetails(testInstance, settingsStack, awpStack, details, fn);
51
- insideTopLevelDescribe = false;
52
- };
53
- const pwDetails = toPlaywrightDetails(details);
54
- internals.describe(type, location, suiteName, pwDetails, suite);
76
+ // stack and inject beforeEach. Wraps the callback so nested describes inherit.
77
+ insideRootDescribe = true;
78
+ try {
79
+ withDescribeScope(title, details, () => withDescribeDetails(testInstance, settingsStack, awpStack, details, callback));
80
+ }
81
+ finally {
82
+ // Reset even if the body throws, so the next spec file starts outside again.
83
+ insideRootDescribe = false;
84
+ }
85
+ });
55
86
  };
87
+ return describeFunction;
56
88
  };
57
89
  const makeTest = (name, register) => {
58
- return (...args) => {
90
+ const testFunction = (...args) => {
91
+ const location = getCallerLocation(testFunction);
92
+ const [title, detailsOrBody, bodyArg] = args;
59
93
  // Only the `(title, ...)` forms register a test; the title-less forms
60
94
  // (`test.skip()`, `test.skip(condition)`, ...) are used inside tests and describes.
61
- if (typeof args[0] === "string" && !insideTopLevelDescribe) {
62
- throw new Error(`${name}("${args[0]}") must be nested inside a test.topLevelDescribe() block.`);
95
+ if (typeof title !== "string") {
96
+ register(location, ...args);
97
+ return;
98
+ }
99
+ if (!insideRootDescribe) {
100
+ throw new Error(`${name}("${title}") must be nested inside a test.describe() block.`);
63
101
  }
64
- register(getCallerLocation(), ...args);
102
+ const details = typeof detailsOrBody === "function"
103
+ ? undefined
104
+ : detailsOrBody;
105
+ const body = typeof detailsOrBody === "function" ? detailsOrBody : bodyArg;
106
+ assertValidConditions(details?.conditions, `${name}("${title}")`);
107
+ const conditions = combineConditions(testConditions, conditionsStack[conditionsStack.length - 1], details?.conditions);
108
+ assertRequiredConditions(testConditions, conditions, `${name}("${title}")`, location.file);
109
+ if (!shouldRunTest(testConditions, conditions, location.file, [...titleStack, title])) {
110
+ // Conditions not met: register as skipped, whatever the variant (`only`, `fail`, ...).
111
+ internals.modifier("skip", location, title, details ? toPlaywrightDetails(details) : {}, body);
112
+ return;
113
+ }
114
+ if (!details) {
115
+ register(location, title, body);
116
+ return;
117
+ }
118
+ withTestDetails(testInstance, settingsStack, awpStack, location, title, details);
119
+ register(location, title, toPlaywrightDetails(details), body);
65
120
  };
121
+ return testFunction;
66
122
  };
67
123
  return Object.assign(makeTest("test", (location, ...args) => internals.createTest("default", location, ...args)), testInstance, {
68
124
  only: makeTest("test.only", (location, ...args) => internals.createTest("only", location, ...args)),
@@ -71,15 +127,17 @@ export function createTest(options) {
71
127
  fail: Object.assign(makeTest("test.fail", (location, ...args) => internals.modifier("fail", location, ...args)), testInstance.fail, {
72
128
  only: makeTest("test.fail.only", (location, ...args) => internals.createTest("fail.only", location, ...args)),
73
129
  }),
74
- /** Build the overridden `test.describe` (with its `.skip`, `.only`, `.fixme`) for a single `createTest` instance. */
75
- describe: Object.assign(makeDescribe("default", "describe"), testInstance.describe, {
76
- only: makeDescribe("only", "describe.only"),
77
- fixme: makeDescribe("fixme", "describe.fixme"),
78
- skip: makeDescribe("skip", "describe.skip"),
79
- }),
80
- /** Build `test.topLevelDescribe` (with its `.skip`) for a single `createTest` instance. */
81
- topLevelDescribe: Object.assign(makeTopLevelDescribe("default"), {
82
- skip: makeTopLevelDescribe("skip"),
130
+ /** Build the overridden `test.describe` (with its variants) for a single `createTest` instance. */
131
+ describe: Object.assign(makeDescribe("default", "test.describe"), testInstance.describe, {
132
+ only: makeDescribe("only", "test.describe.only"),
133
+ fixme: makeDescribe("fixme", "test.describe.fixme"),
134
+ skip: makeDescribe("skip", "test.describe.skip"),
135
+ serial: Object.assign(makeDescribe("serial", "test.describe.serial"), {
136
+ only: makeDescribe("serial.only", "test.describe.serial.only"),
137
+ }),
138
+ parallel: Object.assign(makeDescribe("parallel", "test.describe.parallel"), {
139
+ only: makeDescribe("parallel.only", "test.describe.parallel.only"),
140
+ }),
83
141
  }),
84
142
  });
85
143
  }
@@ -1,4 +1,4 @@
1
- import type { PlaywrightTest } from "./playwright-types.js";
1
+ import type { PlaywrightTestInstance } from "./playwright-types.js";
2
2
  /**
3
3
  * Source location Playwright records for tests, describes and modifiers.
4
4
  */
@@ -21,13 +21,18 @@ export interface IPlaywrightInternals {
21
21
  /**
22
22
  * Get the private `TestTypeImpl` behind a Playwright `test` function.
23
23
  */
24
- export declare function getPlaywrightInternals(testInstance: PlaywrightTest): IPlaywrightInternals;
24
+ export declare function getPlaywrightInternals(testInstance: PlaywrightTestInstance): IPlaywrightInternals;
25
25
  /**
26
- * Location of the first stack frame outside this package, i.e. the spec file (or consumer helper)
27
- * that called our wrapper. Same frame Playwright's public API would have recorded.
26
+ * Location of whoever called `wrapper` (the spec file, or a consumer helper): the same frame
27
+ * Playwright's public API would have recorded.
28
+ *
29
+ * Frames are cut by function identity (`Error.captureStackTrace` omits `wrapper` and everything above it),
30
+ * not by file path, so this keeps working when consumers bundle this package into their own chunks.
28
31
  *
29
32
  * Playwright installs source-map-support while loading test files, so the stack already shows
30
33
  * the original TypeScript file and line.
34
+ *
35
+ * @param wrapper - our public function the spec called (`test`, `test.describe`, ...); must be on the stack
31
36
  */
32
- export declare function getCallerLocation(): IPlaywrightLocation;
37
+ export declare function getCallerLocation(wrapper: (...args: never[]) => unknown): IPlaywrightLocation;
33
38
  //# sourceMappingURL=playwright-internals.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-internals.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/playwright-internals.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAU5D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC;AACxE,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAC/D,MAAM,MAAM,sBAAsB,GAC5B,SAAS,GACT,MAAM,GACN,MAAM,GACN,OAAO,GACP,QAAQ,GACR,aAAa,GACb,UAAU,GACV,eAAe,CAAC;AAEtB,MAAM,WAAW,oBAAoB;IACjC,oEAAoE;IACpE,UAAU,CAAC,IAAI,EAAE,wBAAwB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACpG,+GAA+G;IAC/G,QAAQ,CAAC,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChG,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CACnG;AAoBD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,cAAc,GAAG,oBAAoB,CAkBzF;AAWD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,mBAAmB,CAkBvD"}
1
+ {"version":3,"file":"playwright-internals.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/playwright-internals.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAUpE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC;AACxE,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAC/D,MAAM,MAAM,sBAAsB,GAC5B,SAAS,GACT,MAAM,GACN,MAAM,GACN,OAAO,GACP,QAAQ,GACR,aAAa,GACb,UAAU,GACV,eAAe,CAAC;AAEtB,MAAM,WAAW,oBAAoB;IACjC,oEAAoE;IACpE,UAAU,CAAC,IAAI,EAAE,wBAAwB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACpG,+GAA+G;IAC/G,QAAQ,CAAC,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChG,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CACnG;AAoBD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,sBAAsB,GAAG,oBAAoB,CAkBjG;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,GAAG,mBAAmB,CAmB7F"}
@@ -27,27 +27,31 @@ export function getPlaywrightInternals(testInstance) {
27
27
  describe: (type, location, ...args) => impl._describe(type, location, ...args),
28
28
  };
29
29
  }
30
- // This package's code: compiled (`esm/`) and, when source-mapped, original (`src/`). Frames from here are our own wrappers.
31
- const OWN_CODE_DIRS = ["../../../esm/", "../../../src/"].map((dir) => fileURLToPath(new URL(dir, import.meta.url)));
32
30
  // Matches the location at the end of a stack line: "at fn (/path/file.ts:12:3)", "at /path/file.ts:12:3",
33
31
  // "at fn (file:///path/file.ts:12:3)".
34
32
  const STACK_FRAME_LOCATION = /\(?((?:file:\/\/)?[^\s()]+):(\d+):(\d+)\)?$/;
35
33
  /**
36
- * Location of the first stack frame outside this package, i.e. the spec file (or consumer helper)
37
- * that called our wrapper. Same frame Playwright's public API would have recorded.
34
+ * Location of whoever called `wrapper` (the spec file, or a consumer helper): the same frame
35
+ * Playwright's public API would have recorded.
36
+ *
37
+ * Frames are cut by function identity (`Error.captureStackTrace` omits `wrapper` and everything above it),
38
+ * not by file path, so this keeps working when consumers bundle this package into their own chunks.
38
39
  *
39
40
  * Playwright installs source-map-support while loading test files, so the stack already shows
40
41
  * the original TypeScript file and line.
42
+ *
43
+ * @param wrapper - our public function the spec called (`test`, `test.describe`, ...); must be on the stack
41
44
  */
42
- export function getCallerLocation() {
43
- const stack = new Error().stack ?? "";
44
- for (const stackLine of stack.split("\n").slice(1)) {
45
+ export function getCallerLocation(wrapper) {
46
+ const target = {};
47
+ Error.captureStackTrace(target, wrapper);
48
+ for (const stackLine of (target.stack ?? "").split("\n").slice(1)) {
45
49
  const match = STACK_FRAME_LOCATION.exec(stackLine.trim());
46
50
  if (!match) {
47
51
  continue;
48
52
  }
49
53
  const file = match[1].startsWith("file://") ? fileURLToPath(match[1]) : match[1];
50
- if (OWN_CODE_DIRS.some((dir) => file.startsWith(dir)) || file.startsWith("node:")) {
54
+ if (file.startsWith("node:")) {
51
55
  continue;
52
56
  }
53
57
  return { file, line: Number(match[2]), column: Number(match[3]) };
@@ -1,4 +1,4 @@
1
- import type { PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, test } from "@playwright/test";
1
+ import type { PlaywrightTestArgs, PlaywrightTestOptions, TestType as PlaywrightTestType, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo } from "@playwright/test";
2
2
  /**
3
3
  * @internal
4
4
  */
@@ -10,17 +10,25 @@ export type PlaywrightBaseWorkerArgs = PlaywrightWorkerArgs & PlaywrightWorkerOp
10
10
  /**
11
11
  * @internal
12
12
  */
13
- export type PlaywrightTest = typeof test;
13
+ export type { PlaywrightTestType };
14
14
  /**
15
15
  * @internal
16
16
  */
17
- export type PlaywrightDescribeConfigure = typeof test.describe.configure;
17
+ export type PlaywrightTestInstance = PlaywrightTestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
18
18
  /**
19
19
  * @internal
20
20
  */
21
- export type PlaywrightDescribeSerial = typeof test.describe.serial;
21
+ export type PlaywrightDescribeConfigure = PlaywrightTestInstance["describe"]["configure"];
22
22
  /**
23
23
  * @internal
24
+ * Body of a test: `(fixtures, testInfo) => ...`. Mirrors Playwright's internal `TestBody<TestArgs>`,
25
+ * which `@playwright/test` doesn't export.
24
26
  */
25
- export type PlaywrightDescribeParallel = typeof test.describe.parallel;
27
+ export type PlaywrightTestBody<TestArgs> = (args: TestArgs, testInfo: TestInfo) => Promise<unknown> | unknown;
28
+ /**
29
+ * @internal
30
+ * Condition callback of `test.skip(callback, description?)` and friends. Mirrors Playwright's internal
31
+ * `ConditionBody<TestArgs>`, which `@playwright/test` doesn't export.
32
+ */
33
+ export type PlaywrightConditionBody<TestArgs> = (args: TestArgs) => boolean;
26
34
  //# sourceMappingURL=playwright-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-types.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/playwright-types.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACR,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,IAAI,EACP,MAAM,kBAAkB,CAAC;AAE1B;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,GAAG,qBAAqB,CAAC;AAEhF;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,oBAAoB,GAAG,uBAAuB,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,IAAI,CAAC;AAEzC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC"}
1
+ {"version":3,"file":"playwright-types.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/playwright-types.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACR,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,IAAI,kBAAkB,EAC9B,oBAAoB,EACpB,uBAAuB,EACvB,QAAQ,EACX,MAAM,kBAAkB,CAAC;AAE1B;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,GAAG,qBAAqB,CAAC;AAEhF;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,oBAAoB,GAAG,uBAAuB,CAAC;AAEtF;;GAEG;AACH,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CACnD,kBAAkB,GAAG,qBAAqB,EAC1C,oBAAoB,GAAG,uBAAuB,CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,CAAC;AAE1F;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,CAAC,QAAQ,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAE9G;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,CAAC,QAAQ,IAAI,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { ITestConditions, TestConditionsValueConstraint } from "./types.js";
2
+ /** Combine a describe's or test's own conditions with the inherited ones (`child` replaces `parent` by default). */
3
+ export declare function combineConditions<TestConditionsValue extends TestConditionsValueConstraint>(testConditions: ITestConditions<TestConditionsValue> | undefined, parent: TestConditionsValue | undefined, child: TestConditionsValue | undefined): TestConditionsValue | undefined;
4
+ /**
5
+ * Throw if `conditions` is set (not `undefined`) but empty or of a type that isn't allowed. Empty values
6
+ * shouldn't be set at all. `0` is a valid number; objects other than plain objects, arrays, `Map` and
7
+ * `Set` (class instances, `Date`, ...) aren't checked for emptiness.
8
+ *
9
+ * @param conditions - the value from `details.conditions`
10
+ * @param where - call description used in the error message, e.g. `test.describe("title")`
11
+ */
12
+ export declare function assertValidConditions(conditions: unknown, where: string): void;
13
+ /**
14
+ * Throw if `testConditions.required` is set and a test ends up without conditions, neither its own
15
+ * nor inherited from an enclosing describe.
16
+ *
17
+ * @param where - call description used in the error message, e.g. `test("title")`
18
+ */
19
+ export declare function assertRequiredConditions<TestConditionsValue extends TestConditionsValueConstraint>(testConditions: ITestConditions<TestConditionsValue> | undefined, conditions: TestConditionsValue | undefined, where: string, file: string): void;
20
+ /**
21
+ * Whether a test runs. Without conditions anywhere in its chain (or without `testConditions`
22
+ * configured), the predicate isn't called and the test runs.
23
+ */
24
+ export declare function shouldRunTest<TestConditionsValue extends TestConditionsValueConstraint>(testConditions: ITestConditions<TestConditionsValue> | undefined, conditions: TestConditionsValue | undefined, file: string, titlePath: string[]): boolean;
25
+ //# sourceMappingURL=test-conditions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-conditions.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/test-conditions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAEjF,oHAAoH;AACpH,wBAAgB,iBAAiB,CAAC,mBAAmB,SAAS,6BAA6B,EACvF,cAAc,EAAE,eAAe,CAAC,mBAAmB,CAAC,GAAG,SAAS,EAChE,MAAM,EAAE,mBAAmB,GAAG,SAAS,EACvC,KAAK,EAAE,mBAAmB,GAAG,SAAS,GACvC,mBAAmB,GAAG,SAAS,CAKjC;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAS9E;AAgCD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,mBAAmB,SAAS,6BAA6B,EAC9F,cAAc,EAAE,eAAe,CAAC,mBAAmB,CAAC,GAAG,SAAS,EAChE,UAAU,EAAE,mBAAmB,GAAG,SAAS,EAC3C,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACb,IAAI,CAON;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,mBAAmB,SAAS,6BAA6B,EACnF,cAAc,EAAE,eAAe,CAAC,mBAAmB,CAAC,GAAG,SAAS,EAChE,UAAU,EAAE,mBAAmB,GAAG,SAAS,EAC3C,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EAAE,GACpB,OAAO,CAKT"}
@@ -0,0 +1,76 @@
1
+ // (C) 2026 GoodData Corporation
2
+ /** Combine a describe's or test's own conditions with the inherited ones (`child` replaces `parent` by default). */
3
+ export function combineConditions(testConditions, parent, child) {
4
+ if (child === undefined) {
5
+ return parent;
6
+ }
7
+ return testConditions?.merge ? testConditions.merge(parent, child) : child;
8
+ }
9
+ /**
10
+ * Throw if `conditions` is set (not `undefined`) but empty or of a type that isn't allowed. Empty values
11
+ * shouldn't be set at all. `0` is a valid number; objects other than plain objects, arrays, `Map` and
12
+ * `Set` (class instances, `Date`, ...) aren't checked for emptiness.
13
+ *
14
+ * @param conditions - the value from `details.conditions`
15
+ * @param where - call description used in the error message, e.g. `test.describe("title")`
16
+ */
17
+ export function assertValidConditions(conditions, where) {
18
+ if (conditions === undefined) {
19
+ return;
20
+ }
21
+ const problem = getConditionsProblem(conditions);
22
+ if (problem) {
23
+ throw new Error(`${where}: \`conditions\` ${problem}. Leave \`conditions\` out instead.`);
24
+ }
25
+ }
26
+ function getConditionsProblem(conditions) {
27
+ // `null` and `boolean` are already rejected by the types; this catches values cast past them.
28
+ if (conditions === null) {
29
+ return "is null";
30
+ }
31
+ if (typeof conditions === "boolean") {
32
+ return "is a boolean, which isn't an allowed conditions value";
33
+ }
34
+ if (typeof conditions === "string") {
35
+ return conditions === "" ? "is set but empty (empty string)" : undefined;
36
+ }
37
+ if (typeof conditions === "number") {
38
+ return Number.isNaN(conditions) ? "is set but not a number (NaN)" : undefined;
39
+ }
40
+ if (Array.isArray(conditions)) {
41
+ return conditions.length === 0 ? "is set but empty (empty array)" : undefined;
42
+ }
43
+ if (conditions instanceof Map || conditions instanceof Set) {
44
+ return conditions.size === 0 ? `is set but empty (empty ${conditions.constructor.name})` : undefined;
45
+ }
46
+ if (typeof conditions === "object") {
47
+ const prototype = Object.getPrototypeOf(conditions);
48
+ const isPlainObject = prototype === Object.prototype || prototype === null;
49
+ return isPlainObject && Object.keys(conditions).length === 0
50
+ ? "is set but empty (object without keys)"
51
+ : undefined;
52
+ }
53
+ return undefined;
54
+ }
55
+ /**
56
+ * Throw if `testConditions.required` is set and a test ends up without conditions, neither its own
57
+ * nor inherited from an enclosing describe.
58
+ *
59
+ * @param where - call description used in the error message, e.g. `test("title")`
60
+ */
61
+ export function assertRequiredConditions(testConditions, conditions, where, file) {
62
+ if (testConditions?.required && conditions === undefined) {
63
+ throw new Error(`${where}: \`testConditions.required\` is set, but neither this test nor any enclosing ` +
64
+ `test.describe() sets \`conditions\` (${file}).`);
65
+ }
66
+ }
67
+ /**
68
+ * Whether a test runs. Without conditions anywhere in its chain (or without `testConditions`
69
+ * configured), the predicate isn't called and the test runs.
70
+ */
71
+ export function shouldRunTest(testConditions, conditions, file, titlePath) {
72
+ if (!testConditions || conditions === undefined) {
73
+ return true;
74
+ }
75
+ return testConditions.shouldRun(conditions, { file, titlePath });
76
+ }
@@ -1,5 +1,5 @@
1
1
  import type { TestDetails } from "@playwright/test";
2
- import type { ITestDetails } from "./types.js";
2
+ import type { ITestDetails, TestConditionsValueConstraint } from "./types.js";
3
3
  /** Strip custom fields before passing details to Playwright. */
4
- export declare function toPlaywrightDetails(details: ITestDetails): TestDetails;
4
+ export declare function toPlaywrightDetails(details: ITestDetails<TestConditionsValueConstraint>): TestDetails;
5
5
  //# sourceMappingURL=to-playwright-details.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"to-playwright-details.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/to-playwright-details.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW,CAItE"}
1
+ {"version":3,"file":"to-playwright-details.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/to-playwright-details.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,YAAY,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAE9E,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,6BAA6B,CAAC,GAAG,WAAW,CAIrG"}
@@ -2,6 +2,6 @@
2
2
  /** Strip custom fields before passing details to Playwright. */
3
3
  export function toPlaywrightDetails(details) {
4
4
  // oxlint-disable-next-line @typescript-eslint/no-unused-vars
5
- const { workspaceSettings: _ws, additionalWindowProperties: _awp, ...rest } = details;
5
+ const { workspaceSettings: _ws, additionalWindowProperties: _awp, conditions: _c, ...rest } = details;
6
6
  return Object.keys(rest).length > 0 ? rest : {};
7
7
  }
@@ -1,55 +1,97 @@
1
1
  import type { Fixtures, TestDetails } from "@playwright/test";
2
2
  import type { IFeatureHubEnvironment } from "../feature-hub/types.js";
3
3
  import type { IGoodmockOptions } from "../goodmock/types.js";
4
- import type { PlaywrightDescribeConfigure, PlaywrightDescribeParallel, PlaywrightDescribeSerial, PlaywrightTest } from "./playwright-types.js";
4
+ import type { PlaywrightConditionBody, PlaywrightDescribeConfigure, PlaywrightTestBody, PlaywrightTestType } from "./playwright-types.js";
5
5
  /**
6
6
  * @internal
7
7
  */
8
8
  export type Callback = () => void;
9
9
  /**
10
10
  * @internal
11
- * Call signatures of the overridden `test.describe`, `test.describe.only`, `test.describe.skip` and `test.describe.fixme`.
11
+ * Call signatures of the overridden `test.describe` and its variants (`.only`, `.skip`, `.fixme`, `.serial`, `.parallel`, ...).
12
+ * The outermost describe of a spec file also sets up the spec (FeatureHub mock, goodmock hooks).
12
13
  */
13
- export interface IDescribeFunction {
14
- (title: string, details: ITestDetails, callback: Callback): void;
14
+ export interface IDescribeFunction<TestConditionsValue extends TestConditionsValueConstraint = never> {
15
+ (title: string, details: ITestDetails<TestConditionsValue>, callback: Callback): void;
15
16
  }
16
17
  /**
17
18
  * @internal
19
+ * `test.describe.serial` / `test.describe.parallel`, with their `.only`.
18
20
  */
19
- export interface IDescribe extends IDescribeFunction {
20
- skip: IDescribeFunction;
21
- only: IDescribeFunction;
21
+ export interface IDescribeModeFunction<TestConditionsValue extends TestConditionsValueConstraint = never> extends IDescribeFunction<TestConditionsValue> {
22
+ only: IDescribeFunction<TestConditionsValue>;
23
+ }
24
+ /**
25
+ * @internal
26
+ */
27
+ export interface IDescribe<TestConditionsValue extends TestConditionsValueConstraint = never> extends IDescribeFunction<TestConditionsValue> {
28
+ skip: IDescribeFunction<TestConditionsValue>;
29
+ only: IDescribeFunction<TestConditionsValue>;
22
30
  configure: PlaywrightDescribeConfigure;
23
- fixme: IDescribeFunction;
24
- serial: PlaywrightDescribeSerial;
25
- parallel: PlaywrightDescribeParallel;
31
+ fixme: IDescribeFunction<TestConditionsValue>;
32
+ serial: IDescribeModeFunction<TestConditionsValue>;
33
+ parallel: IDescribeModeFunction<TestConditionsValue>;
26
34
  }
27
35
  /**
28
36
  * @internal
29
- * Call signatures of the overridden `test.describe` and `test.describe.skip`.
37
+ * Allowed types for the consumer's conditions value: objects (including arrays), strings and numbers.
38
+ * Not `boolean`, `null` or `undefined` (nor unions containing them). Only the top level is restricted,
39
+ * an object may contain booleans. Without conditions configured, the conditions type is `never`.
40
+ */
41
+ export type TestConditionsValueConstraint = object | string | number;
42
+ /**
43
+ * @internal
44
+ * Context passed to {@link ITestConditions.shouldRun}, for the test being registered.
30
45
  */
31
- export interface ITopLevelDescribeFunction {
32
- (suiteName: string, specName: string, details: ITestDetails, fn: Callback): void;
46
+ export interface ITestConditionsContext {
47
+ /** Absolute path of the spec file. */
48
+ file: string;
49
+ /** Titles of the enclosing describes, outermost first, followed by the test's own title. */
50
+ titlePath: string[];
33
51
  }
34
52
  /**
35
53
  * @internal
54
+ * Consumer-defined conditions deciding which tests run. `TestConditionsValue` is the consumer's own type (e.g. a list
55
+ * of products). Describes and tests set it via `details.conditions`; nested values are combined with
56
+ * {@link ITestConditions.merge}. Each test is evaluated once, while tests are being registered.
36
57
  */
37
- export interface ITopLevelDescribe extends ITopLevelDescribeFunction {
38
- skip: ITopLevelDescribeFunction;
58
+ export interface ITestConditions<TestConditionsValue extends TestConditionsValueConstraint> {
59
+ /**
60
+ * Whether a test with these (already combined) conditions runs. When `false`, the test is registered
61
+ * as skipped. Not called when neither the test nor any enclosing describe sets conditions.
62
+ *
63
+ * Called while tests are being registered, in every Playwright process: it must be synchronous and
64
+ * return the same answer every time (env vars and config are fine; async or runtime state are not).
65
+ */
66
+ shouldRun: (conditions: TestConditionsValue, context: ITestConditionsContext) => boolean;
67
+ /**
68
+ * Combine a describe's or test's conditions with those inherited from its enclosing describes.
69
+ * Defaults to `child` replacing `parent`.
70
+ */
71
+ merge?: (parent: TestConditionsValue | undefined, child: TestConditionsValue) => TestConditionsValue;
72
+ /**
73
+ * When `true`, every test must end up with conditions: set on the test itself or inherited from an
74
+ * enclosing describe. Registering a test without any throws. Defaults to `false`.
75
+ */
76
+ required?: boolean;
39
77
  }
40
78
  /**
41
79
  * @internal
42
80
  */
43
- export interface ICustomCreateTestOptions {
81
+ export interface ICustomCreateTestOptions<TestConditionsValue extends TestConditionsValueConstraint = never> {
44
82
  featureHubResponse: IFeatureHubEnvironment[];
45
83
  goodmock?: IGoodmockOptions;
84
+ testConditions?: ITestConditions<TestConditionsValue>;
46
85
  }
47
86
  /**
48
87
  * @internal
88
+ * `testConditions` is required when the conditions type `TestConditionsValue` is given (or inferred from it).
49
89
  */
50
- export interface ICreateTestOptions<T extends {} = {}, W extends {} = {}> extends ICustomCreateTestOptions {
90
+ export type ICreateTestOptions<TestConditionsValue extends TestConditionsValueConstraint = never, T extends {} = {}, W extends {} = {}> = ICustomCreateTestOptions<TestConditionsValue> & {
51
91
  fixtures?: Fixtures<T, W>;
52
- }
92
+ } & ([TestConditionsValue] extends [never] ? {} : {
93
+ testConditions: ITestConditions<TestConditionsValue>;
94
+ });
53
95
  /**
54
96
  * @internal
55
97
  */
@@ -61,31 +103,46 @@ export type WindowProperties = Record<string, unknown>;
61
103
  /**
62
104
  * @internal
63
105
  */
64
- export interface ITestDetails extends TestDetails {
106
+ export interface ITestDetails<TestConditionsValue extends TestConditionsValueConstraint = never> extends TestDetails {
65
107
  workspaceSettings?: WorkspaceSettings;
66
108
  additionalWindowProperties?: WindowProperties;
109
+ /** See {@link ITestConditions}. */
110
+ conditions?: TestConditionsValue;
67
111
  }
68
112
  /**
69
113
  * @internal
70
- * Keep only the call signatures of an overloaded function type (Playwright's `test` has exactly two).
114
+ * Call signatures that register a test: `test`, `test.only`, `test.fail.only`.
115
+ * Same as Playwright's, except `details` also accepts `workspaceSettings` / `additionalWindowProperties` / `conditions`.
71
116
  */
72
- export type CallSignatures<F> = F extends {
73
- (...args: infer A1): infer R1;
74
- (...args: infer A2): infer R2;
75
- } ? {
76
- (...args: A1): R1;
77
- (...args: A2): R2;
78
- } : never;
117
+ export interface ITestFunction<TestConditionsValue extends TestConditionsValueConstraint, TestArgs extends {}, WorkerArgs extends {}> {
118
+ (title: string, body: PlaywrightTestBody<TestArgs & WorkerArgs>): void;
119
+ (title: string, details: ITestDetails<TestConditionsValue>, body: PlaywrightTestBody<TestArgs & WorkerArgs>): void;
120
+ }
79
121
  /**
80
122
  * @internal
81
- * Like `T & U`, but properties in `U` replace those in `T` instead of merging, and `T` stays callable.
123
+ * `test.skip` / `test.fixme`: register a skipped test (title forms), or mark the current test or describe.
82
124
  */
83
- export type Override<T, U> = CallSignatures<T> & Omit<T, keyof U> & U;
125
+ export interface ITestModifierFunction<TestConditionsValue extends TestConditionsValueConstraint, TestArgs extends {}, WorkerArgs extends {}> extends ITestFunction<TestConditionsValue, TestArgs, WorkerArgs> {
126
+ (): void;
127
+ (condition: boolean, description?: string): void;
128
+ (callback: PlaywrightConditionBody<TestArgs & WorkerArgs>, description?: string): void;
129
+ }
130
+ /**
131
+ * @internal
132
+ * `test.fail`, with its `.only`.
133
+ */
134
+ export interface ITestFailFunction<TestConditionsValue extends TestConditionsValueConstraint, TestArgs extends {}, WorkerArgs extends {}> extends ITestModifierFunction<TestConditionsValue, TestArgs, WorkerArgs> {
135
+ only: ITestFunction<TestConditionsValue, TestArgs, WorkerArgs>;
136
+ }
84
137
  /**
85
138
  * @internal
139
+ * Playwright's `TestType<TestArgs, WorkerArgs>`, with our `test` / `only` / `skip` / `fixme` / `fail` / `describe`.
86
140
  */
87
- export type Test = Override<PlaywrightTest, {
88
- topLevelDescribe: ITopLevelDescribe;
89
- describe: IDescribe;
90
- }>;
141
+ export type Test<TestConditionsValue extends TestConditionsValueConstraint, TestArgs extends {}, WorkerArgs extends {}> = ITestFunction<TestConditionsValue, TestArgs, WorkerArgs> & Omit<PlaywrightTestType<TestArgs, WorkerArgs>, "only" | "skip" | "fixme" | "fail" | "describe"> & {
142
+ only: ITestFunction<TestConditionsValue, TestArgs, WorkerArgs>;
143
+ skip: ITestModifierFunction<TestConditionsValue, TestArgs, WorkerArgs>;
144
+ fixme: ITestModifierFunction<TestConditionsValue, TestArgs, WorkerArgs>;
145
+ fail: ITestFailFunction<TestConditionsValue, TestArgs, WorkerArgs>;
146
+ describe: IDescribe<TestConditionsValue>;
147
+ };
91
148
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/types.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EACR,2BAA2B,EAC3B,0BAA0B,EAC1B,wBAAwB,EACxB,cAAc,EACjB,MAAM,uBAAuB,CAAC;AAE/B;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC;AAElC;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAC9B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;CACpE;AAED;;GAEG;AACH,MAAM,WAAW,SAAU,SAAQ,iBAAiB;IAChD,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,iBAAiB,CAAC;IACxB,SAAS,EAAE,2BAA2B,CAAC;IACvC,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,EAAE,wBAAwB,CAAC;IACjC,QAAQ,EAAE,0BAA0B,CAAC;CACxC;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACtC,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAAC;CACpF;AAED;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,yBAAyB;IAChE,IAAI,EAAE,yBAAyB,CAAC;CAOnC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACrC,kBAAkB,EAAE,sBAAsB,EAAE,CAAC;IAC7C,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAE,SAAQ,wBAAwB;IACtG,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAExD;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC7C,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,0BAA0B,CAAC,EAAE,gBAAgB,CAAC;CACjD;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IAAC,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAA;CAAE,GACpG;IAAE,CAAC,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC;IAAC,CAAC,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,CAAA;CAAE,GACxC,KAAK,CAAC;AAEZ;;;GAGG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;AAEtE;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG,QAAQ,CACvB,cAAc,EACd;IACI,gBAAgB,EAAE,iBAAiB,CAAC;IACpC,QAAQ,EAAE,SAAS,CAAC;CACvB,CACJ,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/types.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,12 @@
1
- import type { PlaywrightTest } from "./playwright-types.js";
2
- import type { Callback, ITestDetails, WindowProperties, WorkspaceSettings } from "./types.js";
1
+ import type { IPlaywrightLocation } from "./playwright-internals.js";
2
+ import type { PlaywrightTestInstance } from "./playwright-types.js";
3
+ import type { Callback, ITestDetails, TestConditionsValueConstraint, WindowProperties, WorkspaceSettings } from "./types.js";
3
4
  /** Push merged settings/awp onto stacks, register a single beforeEach, call fn, pop. */
4
- export declare function withDescribeDetails(testInstance: PlaywrightTest, settingsStack: WorkspaceSettings[], awpStack: WindowProperties[], details: ITestDetails | undefined, fn: Callback): void;
5
+ export declare function withDescribeDetails(testInstance: PlaywrightTestInstance, settingsStack: WorkspaceSettings[], awpStack: WindowProperties[], details: ITestDetails<TestConditionsValueConstraint> | undefined, fn: Callback): void;
6
+ /**
7
+ * Register a single test's own settings/awp, merged on top of the enclosing describes'.
8
+ * The beforeEach is registered next to the test, after the enclosing describes' injection hooks,
9
+ * so it runs last and its values win.
10
+ */
11
+ export declare function withTestDetails(testInstance: PlaywrightTestInstance, settingsStack: WorkspaceSettings[], awpStack: WindowProperties[], location: IPlaywrightLocation, title: string, details: ITestDetails<TestConditionsValueConstraint> | undefined): void;
5
12
  //# sourceMappingURL=with-describe-details.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"with-describe-details.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/with-describe-details.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAM9F,wFAAwF;AACxF,wBAAgB,mBAAmB,CAC/B,YAAY,EAAE,cAAc,EAC5B,aAAa,EAAE,iBAAiB,EAAE,EAClC,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,OAAO,EAAE,YAAY,GAAG,SAAS,EACjC,EAAE,EAAE,QAAQ,QAmBf"}
1
+ {"version":3,"file":"with-describe-details.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/with-describe-details.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EACR,QAAQ,EACR,YAAY,EACZ,6BAA6B,EAC7B,gBAAgB,EAChB,iBAAiB,EACpB,MAAM,YAAY,CAAC;AAMpB,wFAAwF;AACxF,wBAAgB,mBAAmB,CAC/B,YAAY,EAAE,sBAAsB,EACpC,aAAa,EAAE,iBAAiB,EAAE,EAClC,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,OAAO,EAAE,YAAY,CAAC,6BAA6B,CAAC,GAAG,SAAS,EAChE,EAAE,EAAE,QAAQ,QAsBf;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC3B,YAAY,EAAE,sBAAsB,EACpC,aAAa,EAAE,iBAAiB,EAAE,EAClC,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,CAAC,6BAA6B,CAAC,GAAG,SAAS,GACjE,IAAI,CAsBN"}
@@ -17,23 +17,56 @@ export function withDescribeDetails(testInstance, settingsStack, awpStack, detai
17
17
  if (awp)
18
18
  awpStack.push(mergedAwp);
19
19
  injectWindowProperties(testInstance, mergedSettings, mergedAwp);
20
- fn();
21
- if (awp)
22
- awpStack.pop();
23
- if (ws)
24
- settingsStack.pop();
20
+ try {
21
+ fn();
22
+ }
23
+ finally {
24
+ // Pop even if the body throws, so later describes don't inherit this level's entries.
25
+ if (awp)
26
+ awpStack.pop();
27
+ if (ws)
28
+ settingsStack.pop();
29
+ }
30
+ }
31
+ /**
32
+ * Register a single test's own settings/awp, merged on top of the enclosing describes'.
33
+ * The beforeEach is registered next to the test, after the enclosing describes' injection hooks,
34
+ * so it runs last and its values win.
35
+ */
36
+ export function withTestDetails(testInstance, settingsStack, awpStack, location, title, details) {
37
+ const ws = details?.workspaceSettings;
38
+ const awp = details?.additionalWindowProperties;
39
+ if (!ws && !awp) {
40
+ return;
41
+ }
42
+ const mergedSettings = ws ? { ...peekOrEmpty(settingsStack), ...ws } : peekOrEmpty(settingsStack);
43
+ const mergedAwp = awp ? { ...peekOrEmpty(awpStack), ...awp } : peekOrEmpty(awpStack);
44
+ testInstance.beforeEach(async ({ page }, testInfo) => {
45
+ // A beforeEach applies to every test in its describe; only act for this test. Title is part
46
+ // of the match because tests generated in a loop share one location.
47
+ if (testInfo.title !== title ||
48
+ testInfo.file !== location.file ||
49
+ testInfo.line !== location.line ||
50
+ testInfo.column !== location.column) {
51
+ return;
52
+ }
53
+ await addWindowPropertiesInitScript(page, mergedSettings, mergedAwp);
54
+ });
25
55
  }
26
56
  /** Inject workspace settings and additional window properties via a single addInitScript. */
27
57
  function injectWindowProperties(testInst, settings, awp) {
28
58
  testInst.beforeEach(async ({ page }) => {
29
- await page.addInitScript((args) => {
30
- const w = window;
31
- if (Object.keys(args.s).length > 0) {
32
- w["customWorkspaceSettings"] = args.s;
33
- }
34
- for (const [key, value] of Object.entries(args.a)) {
35
- w[key] = value;
36
- }
37
- }, { s: settings, a: awp });
59
+ await addWindowPropertiesInitScript(page, settings, awp);
38
60
  });
39
61
  }
62
+ async function addWindowPropertiesInitScript(page, settings, awp) {
63
+ await page.addInitScript((args) => {
64
+ const w = window;
65
+ if (Object.keys(args.s).length > 0) {
66
+ w["customWorkspaceSettings"] = args.s;
67
+ }
68
+ for (const [key, value] of Object.entries(args.a)) {
69
+ w[key] = value;
70
+ }
71
+ }, { s: settings, a: awp });
72
+ }
package/esm/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { goodmockMode, GoodmockMode } from "./create-test/goodmock/mode.js";
7
7
  export { snapshotAndSaveRecording, type ISnapshotAndSaveRecordingOptions, } from "./create-test/goodmock/snapshot/snapshot-and-save-recording.js";
8
8
  export { createTest } from "./create-test/playwright/factory.js";
9
9
  export type { Fixtures } from "./create-test/playwright/fixtures.js";
10
- export type { PlaywrightBaseTestArgs, PlaywrightBaseWorkerArgs, PlaywrightDescribeConfigure, PlaywrightDescribeParallel, PlaywrightDescribeSerial, PlaywrightTest, } from "./create-test/playwright/playwright-types.js";
11
- export type { CallSignatures, Callback, ICreateTestOptions, ICustomCreateTestOptions, IDescribe, IDescribeFunction, ITestDetails, ITopLevelDescribe, ITopLevelDescribeFunction, Override, Test, WindowProperties, WorkspaceSettings, } from "./create-test/playwright/types.js";
10
+ export type { PlaywrightBaseTestArgs, PlaywrightBaseWorkerArgs, PlaywrightDescribeConfigure, PlaywrightConditionBody, PlaywrightTestBody, PlaywrightTestType, PlaywrightTestInstance, } from "./create-test/playwright/playwright-types.js";
11
+ export type { Callback, ICreateTestOptions, ICustomCreateTestOptions, IDescribe, IDescribeFunction, IDescribeModeFunction, ITestConditions, ITestConditionsContext, ITestDetails, ITestFailFunction, ITestFunction, ITestModifierFunction, Test, TestConditionsValueConstraint, WindowProperties, WorkspaceSettings, } from "./create-test/playwright/types.js";
12
12
  export { clickByBoundingBox, hoverByBoundingBox } from "./helpers/mouse-actions.js";
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG3E,OAAO,EACH,aAAa,EACb,SAAS,EACT,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACvB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAGrG,YAAY,EACR,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,gBAAgB,GACnB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACH,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EACf,cAAc,GACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EACH,wBAAwB,EACxB,KAAK,gCAAgC,GACxC,MAAM,gEAAgE,CAAC;AAGxE,OAAO,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AACjE,YAAY,EAAE,QAAQ,EAAE,MAAM,sCAAsC,CAAC;AACrE,YAAY,EACR,sBAAsB,EACtB,wBAAwB,EACxB,2BAA2B,EAC3B,0BAA0B,EAC1B,wBAAwB,EACxB,cAAc,GACjB,MAAM,8CAA8C,CAAC;AACtD,YAAY,EACR,cAAc,EACd,QAAQ,EACR,kBAAkB,EAClB,wBAAwB,EACxB,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,yBAAyB,EACzB,QAAQ,EACR,IAAI,EACJ,gBAAgB,EAChB,iBAAiB,GACpB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG3E,OAAO,EACH,aAAa,EACb,SAAS,EACT,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACvB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAGrG,YAAY,EACR,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,gBAAgB,GACnB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACH,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EACf,cAAc,GACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EACH,wBAAwB,EACxB,KAAK,gCAAgC,GACxC,MAAM,gEAAgE,CAAC;AAGxE,OAAO,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AACjE,YAAY,EAAE,QAAQ,EAAE,MAAM,sCAAsC,CAAC;AACrE,YAAY,EACR,sBAAsB,EACtB,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,GACzB,MAAM,8CAA8C,CAAC;AACtD,YAAY,EACR,QAAQ,EACR,kBAAkB,EAClB,wBAAwB,EACxB,SAAS,EACT,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,qBAAqB,EACrB,IAAI,EACJ,6BAA6B,EAC7B,gBAAgB,EAChB,iBAAiB,GACpB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -4,11 +4,12 @@ import { Locator } from '@playwright/test';
4
4
  import { Page } from '@playwright/test';
5
5
  import type { PlaywrightTestArgs } from '@playwright/test';
6
6
  import type { PlaywrightTestOptions } from '@playwright/test';
7
+ import type { TestType as PlaywrightTestType } from '@playwright/test';
7
8
  import type { PlaywrightWorkerArgs } from '@playwright/test';
8
9
  import type { PlaywrightWorkerOptions } from '@playwright/test';
9
10
  import { Route } from '@playwright/test';
10
- import type { test } from '@playwright/test';
11
11
  import type { TestDetails } from '@playwright/test';
12
+ import type { TestInfo } from '@playwright/test';
12
13
 
13
14
  /**
14
15
  * @internal
@@ -40,18 +41,6 @@ export declare const BACKEND_HOST: string;
40
41
  */
41
42
  export declare type Callback = () => void;
42
43
 
43
- /**
44
- * @internal
45
- * Keep only the call signatures of an overloaded function type (Playwright's `test` has exactly two).
46
- */
47
- export declare type CallSignatures<F> = F extends {
48
- (...args: infer A1): infer R1;
49
- (...args: infer A2): infer R2;
50
- } ? {
51
- (...args: A1): R1;
52
- (...args: A2): R2;
53
- } : never;
54
-
55
44
  /**
56
45
  * @internal
57
46
  * Click at the center of the bounding box of the given locator.
@@ -68,7 +57,7 @@ export declare function clickByBoundingBox(page: Page, locator: Locator, options
68
57
  * @internal
69
58
  * @param options -
70
59
  */
71
- export declare function createTest<T extends {} = {}, W extends {} = {}>(options: ICreateTestOptions<T, W>): Test;
60
+ export declare function createTest<TestConditionsValue extends TestConditionsValueConstraint = never, T extends {} = {}, W extends {} = {}>(options: ICreateTestOptions<TestConditionsValue, T, W>): Test<TestConditionsValue, PlaywrightBaseTestArgs & T, PlaywrightBaseWorkerArgs & W>;
72
61
 
73
62
  /**
74
63
  * @internal
@@ -126,37 +115,50 @@ export declare function hoverByBoundingBox(page: Page, locator: Locator, options
126
115
 
127
116
  /**
128
117
  * @internal
118
+ * `testConditions` is required when the conditions type `TestConditionsValue` is given (or inferred from it).
129
119
  */
130
- export declare interface ICreateTestOptions<T extends {} = {}, W extends {} = {}> extends ICustomCreateTestOptions {
120
+ export declare type ICreateTestOptions<TestConditionsValue extends TestConditionsValueConstraint = never, T extends {} = {}, W extends {} = {}> = ICustomCreateTestOptions<TestConditionsValue> & {
131
121
  fixtures?: Fixtures_2<T, W>;
132
- }
122
+ } & ([TestConditionsValue] extends [never] ? {} : {
123
+ testConditions: ITestConditions<TestConditionsValue>;
124
+ });
133
125
 
134
126
  /**
135
127
  * @internal
136
128
  */
137
- export declare interface ICustomCreateTestOptions {
129
+ export declare interface ICustomCreateTestOptions<TestConditionsValue extends TestConditionsValueConstraint = never> {
138
130
  featureHubResponse: IFeatureHubEnvironment[];
139
131
  goodmock?: IGoodmockOptions;
132
+ testConditions?: ITestConditions<TestConditionsValue>;
140
133
  }
141
134
 
142
135
  /**
143
136
  * @internal
144
137
  */
145
- export declare interface IDescribe extends IDescribeFunction {
146
- skip: IDescribeFunction;
147
- only: IDescribeFunction;
138
+ export declare interface IDescribe<TestConditionsValue extends TestConditionsValueConstraint = never> extends IDescribeFunction<TestConditionsValue> {
139
+ skip: IDescribeFunction<TestConditionsValue>;
140
+ only: IDescribeFunction<TestConditionsValue>;
148
141
  configure: PlaywrightDescribeConfigure;
149
- fixme: IDescribeFunction;
150
- serial: PlaywrightDescribeSerial;
151
- parallel: PlaywrightDescribeParallel;
142
+ fixme: IDescribeFunction<TestConditionsValue>;
143
+ serial: IDescribeModeFunction<TestConditionsValue>;
144
+ parallel: IDescribeModeFunction<TestConditionsValue>;
152
145
  }
153
146
 
154
147
  /**
155
148
  * @internal
156
- * Call signatures of the overridden `test.describe`, `test.describe.only`, `test.describe.skip` and `test.describe.fixme`.
149
+ * Call signatures of the overridden `test.describe` and its variants (`.only`, `.skip`, `.fixme`, `.serial`, `.parallel`, ...).
150
+ * The outermost describe of a spec file also sets up the spec (FeatureHub mock, goodmock hooks).
157
151
  */
158
- export declare interface IDescribeFunction {
159
- (title: string, details: ITestDetails, callback: Callback): void;
152
+ export declare interface IDescribeFunction<TestConditionsValue extends TestConditionsValueConstraint = never> {
153
+ (title: string, details: ITestDetails<TestConditionsValue>, callback: Callback): void;
154
+ }
155
+
156
+ /**
157
+ * @internal
158
+ * `test.describe.serial` / `test.describe.parallel`, with their `.only`.
159
+ */
160
+ export declare interface IDescribeModeFunction<TestConditionsValue extends TestConditionsValueConstraint = never> extends IDescribeFunction<TestConditionsValue> {
161
+ only: IDescribeFunction<TestConditionsValue>;
160
162
  }
161
163
 
162
164
  /**
@@ -249,6 +251,12 @@ export declare interface ISecretMapping {
249
251
  export declare interface ISnapshotAndSaveRecordingOptions {
250
252
  /** Source/target workspace ID rewrite(s) applied before mappings are saved. */
251
253
  workspaceIdMappings?: IWorkspaceIdMapping | IWorkspaceIdMapping[];
254
+ /**
255
+ * Template organization name. Every ephemeral organization name in the saved mappings
256
+ * (`ephemeral-{templateOrganization}-{hex}`) is replaced with this bare name so re-recording
257
+ * against a fresh ephemeral org does not churn the recordings. Defaults to "automation".
258
+ */
259
+ templateOrganization?: string;
252
260
  /**
253
261
  * Real backend URL (e.g. "https://example.gooddata.com"). When provided together with
254
262
  * baseUrl, all occurrences are replaced in the saved mappings so that recorded responses
@@ -280,25 +288,78 @@ export declare interface ISnapshotAndSaveRecordingOptions {
280
288
 
281
289
  /**
282
290
  * @internal
291
+ * Consumer-defined conditions deciding which tests run. `TestConditionsValue` is the consumer's own type (e.g. a list
292
+ * of products). Describes and tests set it via `details.conditions`; nested values are combined with
293
+ * {@link ITestConditions.merge}. Each test is evaluated once, while tests are being registered.
283
294
  */
284
- export declare interface ITestDetails extends TestDetails {
295
+ export declare interface ITestConditions<TestConditionsValue extends TestConditionsValueConstraint> {
296
+ /**
297
+ * Whether a test with these (already combined) conditions runs. When `false`, the test is registered
298
+ * as skipped. Not called when neither the test nor any enclosing describe sets conditions.
299
+ *
300
+ * Called while tests are being registered, in every Playwright process: it must be synchronous and
301
+ * return the same answer every time (env vars and config are fine; async or runtime state are not).
302
+ */
303
+ shouldRun: (conditions: TestConditionsValue, context: ITestConditionsContext) => boolean;
304
+ /**
305
+ * Combine a describe's or test's conditions with those inherited from its enclosing describes.
306
+ * Defaults to `child` replacing `parent`.
307
+ */
308
+ merge?: (parent: TestConditionsValue | undefined, child: TestConditionsValue) => TestConditionsValue;
309
+ /**
310
+ * When `true`, every test must end up with conditions: set on the test itself or inherited from an
311
+ * enclosing describe. Registering a test without any throws. Defaults to `false`.
312
+ */
313
+ required?: boolean;
314
+ }
315
+
316
+ /**
317
+ * @internal
318
+ * Context passed to {@link ITestConditions.shouldRun}, for the test being registered.
319
+ */
320
+ export declare interface ITestConditionsContext {
321
+ /** Absolute path of the spec file. */
322
+ file: string;
323
+ /** Titles of the enclosing describes, outermost first, followed by the test's own title. */
324
+ titlePath: string[];
325
+ }
326
+
327
+ /**
328
+ * @internal
329
+ */
330
+ export declare interface ITestDetails<TestConditionsValue extends TestConditionsValueConstraint = never> extends TestDetails {
285
331
  workspaceSettings?: WorkspaceSettings;
286
332
  additionalWindowProperties?: WindowProperties;
333
+ /** See {@link ITestConditions}. */
334
+ conditions?: TestConditionsValue;
335
+ }
336
+
337
+ /**
338
+ * @internal
339
+ * `test.fail`, with its `.only`.
340
+ */
341
+ export declare interface ITestFailFunction<TestConditionsValue extends TestConditionsValueConstraint, TestArgs extends {}, WorkerArgs extends {}> extends ITestModifierFunction<TestConditionsValue, TestArgs, WorkerArgs> {
342
+ only: ITestFunction<TestConditionsValue, TestArgs, WorkerArgs>;
287
343
  }
288
344
 
289
345
  /**
290
346
  * @internal
347
+ * Call signatures that register a test: `test`, `test.only`, `test.fail.only`.
348
+ * Same as Playwright's, except `details` also accepts `workspaceSettings` / `additionalWindowProperties` / `conditions`.
291
349
  */
292
- export declare interface ITopLevelDescribe extends ITopLevelDescribeFunction {
293
- skip: ITopLevelDescribeFunction;
350
+ export declare interface ITestFunction<TestConditionsValue extends TestConditionsValueConstraint, TestArgs extends {}, WorkerArgs extends {}> {
351
+ (title: string, body: PlaywrightTestBody<TestArgs & WorkerArgs>): void;
352
+ (title: string, details: ITestDetails<TestConditionsValue>, body: PlaywrightTestBody<TestArgs & WorkerArgs>): void;
294
353
  }
295
354
 
296
355
  /**
297
356
  * @internal
298
- * Call signatures of the overridden `test.describe` and `test.describe.skip`.
357
+ * `test.skip` / `test.fixme`: register a skipped test (title forms), or mark the current test or describe.
299
358
  */
300
- export declare interface ITopLevelDescribeFunction {
301
- (suiteName: string, specName: string, details: ITestDetails, fn: Callback): void;
359
+ export declare interface ITestModifierFunction<TestConditionsValue extends TestConditionsValueConstraint, TestArgs extends {}, WorkerArgs extends {}> extends ITestFunction<TestConditionsValue, TestArgs, WorkerArgs> {
360
+ (): void;
361
+ (condition: boolean, description?: string): void;
362
+ (callback: PlaywrightConditionBody<TestArgs & WorkerArgs>, description?: string): void;
302
363
  }
303
364
 
304
365
  /**
@@ -325,12 +386,6 @@ export declare function loadMappings(host: string, mappingFilePath: string): Pro
325
386
  */
326
387
  export declare function mockLogRequests(host: string): Promise<void>;
327
388
 
328
- /**
329
- * @internal
330
- * Like `T & U`, but properties in `U` replace those in `T` instead of merging, and `T` stays callable.
331
- */
332
- export declare type Override<T, U> = CallSignatures<T> & Omit<T, keyof U> & U;
333
-
334
389
  /**
335
390
  * @internal
336
391
  */
@@ -343,24 +398,29 @@ export declare type PlaywrightBaseWorkerArgs = PlaywrightWorkerArgs & Playwright
343
398
 
344
399
  /**
345
400
  * @internal
401
+ * Condition callback of `test.skip(callback, description?)` and friends. Mirrors Playwright's internal
402
+ * `ConditionBody<TestArgs>`, which `@playwright/test` doesn't export.
346
403
  */
347
- export declare type PlaywrightDescribeConfigure = typeof test.describe.configure;
404
+ export declare type PlaywrightConditionBody<TestArgs> = (args: TestArgs) => boolean;
348
405
 
349
406
  /**
350
407
  * @internal
351
408
  */
352
- export declare type PlaywrightDescribeParallel = typeof test.describe.parallel;
409
+ export declare type PlaywrightDescribeConfigure = PlaywrightTestInstance["describe"]["configure"];
353
410
 
354
411
  /**
355
412
  * @internal
413
+ * Body of a test: `(fixtures, testInfo) => ...`. Mirrors Playwright's internal `TestBody<TestArgs>`,
414
+ * which `@playwright/test` doesn't export.
356
415
  */
357
- export declare type PlaywrightDescribeSerial = typeof test.describe.serial;
416
+ export declare type PlaywrightTestBody<TestArgs> = (args: TestArgs, testInfo: TestInfo) => Promise<unknown> | unknown;
358
417
 
359
418
  /**
360
419
  * @internal
361
420
  */
362
- declare type PlaywrightTest_2 = typeof test;
363
- export { PlaywrightTest_2 as PlaywrightTest }
421
+ export declare type PlaywrightTestInstance = PlaywrightTestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
422
+
423
+ export { PlaywrightTestType }
364
424
 
365
425
  /**
366
426
  * @internal
@@ -401,11 +461,23 @@ export declare function startRecording(host: string, backendHost: string): Promi
401
461
 
402
462
  /**
403
463
  * @internal
464
+ * Playwright's `TestType<TestArgs, WorkerArgs>`, with our `test` / `only` / `skip` / `fixme` / `fail` / `describe`.
465
+ */
466
+ export declare type Test<TestConditionsValue extends TestConditionsValueConstraint, TestArgs extends {}, WorkerArgs extends {}> = ITestFunction<TestConditionsValue, TestArgs, WorkerArgs> & Omit<PlaywrightTestType<TestArgs, WorkerArgs>, "only" | "skip" | "fixme" | "fail" | "describe"> & {
467
+ only: ITestFunction<TestConditionsValue, TestArgs, WorkerArgs>;
468
+ skip: ITestModifierFunction<TestConditionsValue, TestArgs, WorkerArgs>;
469
+ fixme: ITestModifierFunction<TestConditionsValue, TestArgs, WorkerArgs>;
470
+ fail: ITestFailFunction<TestConditionsValue, TestArgs, WorkerArgs>;
471
+ describe: IDescribe<TestConditionsValue>;
472
+ };
473
+
474
+ /**
475
+ * @internal
476
+ * Allowed types for the consumer's conditions value: objects (including arrays), strings and numbers.
477
+ * Not `boolean`, `null` or `undefined` (nor unions containing them). Only the top level is restricted,
478
+ * an object may contain booleans. Without conditions configured, the conditions type is `never`.
404
479
  */
405
- export declare type Test = Override<PlaywrightTest_2, {
406
- topLevelDescribe: ITopLevelDescribe;
407
- describe: IDescribe;
408
- }>;
480
+ export declare type TestConditionsValueConstraint = object | string | number;
409
481
 
410
482
  /**
411
483
  * @internal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-e2e-utils",
3
- "version": "11.61.0-alpha.1",
3
+ "version": "11.61.0-alpha.2",
4
4
  "description": "GoodData utility functions for Playwright E2E tests",
5
5
  "license": "MIT",
6
6
  "author": "GoodData",
@@ -49,8 +49,8 @@
49
49
  "oxlint-plugin-eslint": "1.82.0",
50
50
  "oxlint-tsgolint": "7.0.2001",
51
51
  "typescript": "7.0.2",
52
- "@gooddata/eslint-config": "11.61.0-alpha.1",
53
- "@gooddata/oxlint-config": "11.61.0-alpha.1"
52
+ "@gooddata/eslint-config": "11.61.0-alpha.2",
53
+ "@gooddata/oxlint-config": "11.61.0-alpha.2"
54
54
  },
55
55
  "scripts": {
56
56
  "_phase:build": "npm run build",