@gooddata/sdk-e2e-utils 11.61.0-alpha.0 → 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.
- package/esm/create-test/feature-hub/mock.d.ts +2 -2
- package/esm/create-test/feature-hub/mock.d.ts.map +1 -1
- package/esm/create-test/goodmock/snapshot/sanitize-organization.d.ts +12 -0
- package/esm/create-test/goodmock/snapshot/sanitize-organization.d.ts.map +1 -0
- package/esm/create-test/goodmock/snapshot/sanitize-organization.js +28 -0
- package/esm/create-test/goodmock/snapshot/snapshot-and-save-recording.d.ts +6 -0
- package/esm/create-test/goodmock/snapshot/snapshot-and-save-recording.d.ts.map +1 -1
- package/esm/create-test/goodmock/snapshot/snapshot-and-save-recording.js +4 -2
- package/esm/create-test/playwright/factory.d.ts +8 -0
- package/esm/create-test/playwright/factory.d.ts.map +1 -0
- package/esm/create-test/playwright/factory.js +143 -0
- package/esm/create-test/playwright/fixtures.d.ts +2 -2
- package/esm/create-test/playwright/fixtures.d.ts.map +1 -1
- package/esm/create-test/playwright/playwright-internals.d.ts +38 -0
- package/esm/create-test/playwright/playwright-internals.d.ts.map +1 -0
- package/esm/create-test/playwright/playwright-internals.js +60 -0
- package/esm/create-test/playwright/playwright-types.d.ts +12 -20
- package/esm/create-test/playwright/playwright-types.d.ts.map +1 -1
- package/esm/create-test/playwright/test-conditions.d.ts +25 -0
- package/esm/create-test/playwright/test-conditions.d.ts.map +1 -0
- package/esm/create-test/playwright/test-conditions.js +76 -0
- package/esm/create-test/playwright/to-playwright-details.d.ts +5 -0
- package/esm/create-test/playwright/to-playwright-details.d.ts.map +1 -0
- package/esm/create-test/playwright/to-playwright-details.js +7 -0
- package/esm/create-test/playwright/types.d.ts +133 -2
- package/esm/create-test/playwright/types.d.ts.map +1 -1
- package/esm/create-test/playwright/with-describe-details.d.ts +10 -4
- package/esm/create-test/playwright/with-describe-details.d.ts.map +1 -1
- package/esm/create-test/playwright/with-describe-details.js +47 -14
- package/esm/index.d.ts +3 -7
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +2 -1
- package/esm/sdk-e2e-utils.d.ts +144 -70
- package/package.json +3 -3
- package/esm/create-test/playwright/call-original-fn.d.ts +0 -17
- package/esm/create-test/playwright/call-original-fn.d.ts.map +0 -1
- package/esm/create-test/playwright/call-original-fn.js +0 -25
- package/esm/create-test/playwright/describe/factory.d.ts +0 -27
- package/esm/create-test/playwright/describe/factory.d.ts.map +0 -1
- package/esm/create-test/playwright/describe/factory.js +0 -18
- package/esm/create-test/playwright/test/factory.d.ts +0 -7
- package/esm/create-test/playwright/test/factory.d.ts.map +0 -1
- package/esm/create-test/playwright/test/factory.js +0 -42
- package/esm/create-test/playwright/test/types.d.ts +0 -27
- package/esm/create-test/playwright/test/types.d.ts.map +0 -1
- package/esm/create-test/playwright/test/types.js +0 -2
- package/esm/create-test/playwright/top-level-describe/build-suite-callback.d.ts +0 -9
- package/esm/create-test/playwright/top-level-describe/build-suite-callback.d.ts.map +0 -1
- package/esm/create-test/playwright/top-level-describe/build-suite-callback.js +0 -17
- package/esm/create-test/playwright/top-level-describe/factory.d.ts +0 -8
- package/esm/create-test/playwright/top-level-describe/factory.d.ts.map +0 -1
- package/esm/create-test/playwright/top-level-describe/factory.js +0 -12
- package/esm/create-test/playwright/top-level-describe/types.d.ts +0 -17
- package/esm/create-test/playwright/top-level-describe/types.d.ts.map +0 -1
- package/esm/create-test/playwright/top-level-describe/types.js +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
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:
|
|
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,
|
|
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;
|
|
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
|
|
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);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PlaywrightBaseTestArgs, PlaywrightBaseWorkerArgs } from "./playwright-types.js";
|
|
2
|
+
import type { ICreateTestOptions, Test, TestConditionsValueConstraint } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
* @param options -
|
|
6
|
+
*/
|
|
7
|
+
export declare function createTest<TestConditionsValue extends TestConditionsValueConstraint = never, T extends {} = {}, W extends {} = {}>(options: ICreateTestOptions<TestConditionsValue, T, W>): Test<TestConditionsValue, PlaywrightBaseTestArgs & T, PlaywrightBaseWorkerArgs & W>;
|
|
8
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
// oxlint-disable typescript-eslint/no-empty-object-type
|
|
3
|
+
import { basename } from "path";
|
|
4
|
+
import { test } from "@playwright/test";
|
|
5
|
+
import { registerFeatureHubMock } from "../feature-hub/mock.js";
|
|
6
|
+
import { registerGoodmockHooks } from "../goodmock/playwright.js";
|
|
7
|
+
import { getCallerLocation, getPlaywrightInternals, } from "./playwright-internals.js";
|
|
8
|
+
import { assertRequiredConditions, assertValidConditions, combineConditions, shouldRunTest, } from "./test-conditions.js";
|
|
9
|
+
import { toPlaywrightDetails } from "./to-playwright-details.js";
|
|
10
|
+
import { withDescribeDetails, withTestDetails } from "./with-describe-details.js";
|
|
11
|
+
/**
|
|
12
|
+
* @internal
|
|
13
|
+
* @param options -
|
|
14
|
+
*/
|
|
15
|
+
export function createTest(options) {
|
|
16
|
+
// Tracks whether we're inside the outermost describe of a spec file during synchronous registration.
|
|
17
|
+
let insideRootDescribe = false;
|
|
18
|
+
// Stacks track workspace settings / additionalWindowProperties hierarchy during
|
|
19
|
+
// synchronous describe registration. Each level merges on top of the parent's.
|
|
20
|
+
// The beforeEach at each level captures the fully-merged snapshot, so the
|
|
21
|
+
// innermost beforeEach (which runs last) wins.
|
|
22
|
+
const settingsStack = [];
|
|
23
|
+
const awpStack = [];
|
|
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;
|
|
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();
|
|
50
|
+
const makeDescribe = (type, name) => {
|
|
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;
|
|
58
|
+
}
|
|
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.`);
|
|
68
|
+
}
|
|
69
|
+
specNameFiles.set(specName, location.file);
|
|
70
|
+
internals.describe(type, location, title, pwDetails, () => {
|
|
71
|
+
registerFeatureHubMock(testInstance, options.featureHubResponse);
|
|
72
|
+
if (options.goodmock) {
|
|
73
|
+
registerGoodmockHooks(testInstance, options.goodmock, specName);
|
|
74
|
+
}
|
|
75
|
+
// Workspace settings + additional window properties — merge with parent
|
|
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
|
+
});
|
|
86
|
+
};
|
|
87
|
+
return describeFunction;
|
|
88
|
+
};
|
|
89
|
+
const makeTest = (name, register) => {
|
|
90
|
+
const testFunction = (...args) => {
|
|
91
|
+
const location = getCallerLocation(testFunction);
|
|
92
|
+
const [title, detailsOrBody, bodyArg] = args;
|
|
93
|
+
// Only the `(title, ...)` forms register a test; the title-less forms
|
|
94
|
+
// (`test.skip()`, `test.skip(condition)`, ...) are used inside tests and describes.
|
|
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.`);
|
|
101
|
+
}
|
|
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);
|
|
120
|
+
};
|
|
121
|
+
return testFunction;
|
|
122
|
+
};
|
|
123
|
+
return Object.assign(makeTest("test", (location, ...args) => internals.createTest("default", location, ...args)), testInstance, {
|
|
124
|
+
only: makeTest("test.only", (location, ...args) => internals.createTest("only", location, ...args)),
|
|
125
|
+
skip: makeTest("test.skip", (location, ...args) => internals.modifier("skip", location, ...args)),
|
|
126
|
+
fixme: makeTest("test.fixme", (location, ...args) => internals.modifier("fixme", location, ...args)),
|
|
127
|
+
fail: Object.assign(makeTest("test.fail", (location, ...args) => internals.modifier("fail", location, ...args)), testInstance.fail, {
|
|
128
|
+
only: makeTest("test.fail.only", (location, ...args) => internals.createTest("fail.only", location, ...args)),
|
|
129
|
+
}),
|
|
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
|
+
}),
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Fixtures as PlaywrightFixtures } from "@playwright/test";
|
|
2
|
-
import { type
|
|
2
|
+
import { type PlaywrightBaseTestArgs, type PlaywrightBaseWorkerArgs } from "./playwright-types.js";
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
5
5
|
*/
|
|
6
|
-
export type Fixtures<T extends {} = {}, W extends {} = {}> = PlaywrightFixtures<T, W,
|
|
6
|
+
export type Fixtures<T extends {} = {}, W extends {} = {}> = PlaywrightFixtures<T, W, PlaywrightBaseTestArgs, PlaywrightBaseWorkerArgs>;
|
|
7
7
|
//# sourceMappingURL=fixtures.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/fixtures.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../../src/create-test/playwright/fixtures.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEnG;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,kBAAkB,CAC3E,CAAC,EACD,CAAC,EACD,sBAAsB,EACtB,wBAAwB,CAC3B,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { PlaywrightTestInstance } from "./playwright-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Source location Playwright records for tests, describes and modifiers.
|
|
4
|
+
*/
|
|
5
|
+
export interface IPlaywrightLocation {
|
|
6
|
+
file: string;
|
|
7
|
+
line: number;
|
|
8
|
+
column: number;
|
|
9
|
+
}
|
|
10
|
+
export type PlaywrightCreateTestType = "default" | "only" | "fail.only";
|
|
11
|
+
export type PlaywrightModifierType = "skip" | "fixme" | "fail";
|
|
12
|
+
export type PlaywrightDescribeType = "default" | "only" | "skip" | "fixme" | "serial" | "serial.only" | "parallel" | "parallel.only";
|
|
13
|
+
export interface IPlaywrightInternals {
|
|
14
|
+
/** Same as `test(...)`, `test.only(...)`, `test.fail.only(...)`. */
|
|
15
|
+
createTest(type: PlaywrightCreateTestType, location: IPlaywrightLocation, ...args: unknown[]): void;
|
|
16
|
+
/** Same as `test.skip(...)`, `test.fixme(...)`, `test.fail(...)`, in both their title and title-less forms. */
|
|
17
|
+
modifier(type: PlaywrightModifierType, location: IPlaywrightLocation, ...args: unknown[]): void;
|
|
18
|
+
/** Same as `test.describe(...)` and its variants. */
|
|
19
|
+
describe(type: PlaywrightDescribeType, location: IPlaywrightLocation, ...args: unknown[]): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get the private `TestTypeImpl` behind a Playwright `test` function.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getPlaywrightInternals(testInstance: PlaywrightTestInstance): IPlaywrightInternals;
|
|
25
|
+
/**
|
|
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.
|
|
31
|
+
*
|
|
32
|
+
* Playwright installs source-map-support while loading test files, so the stack already shows
|
|
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
|
|
36
|
+
*/
|
|
37
|
+
export declare function getCallerLocation(wrapper: (...args: never[]) => unknown): IPlaywrightLocation;
|
|
38
|
+
//# sourceMappingURL=playwright-internals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
function isTestTypeImpl(value) {
|
|
4
|
+
if (typeof value !== "object" || value === null) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
const impl = value;
|
|
8
|
+
return (typeof impl._createTest === "function" &&
|
|
9
|
+
typeof impl._modifier === "function" &&
|
|
10
|
+
typeof impl._describe === "function");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get the private `TestTypeImpl` behind a Playwright `test` function.
|
|
14
|
+
*/
|
|
15
|
+
export function getPlaywrightInternals(testInstance) {
|
|
16
|
+
// Playwright stores it under a module-private `Symbol("testType")`, so look it up by description.
|
|
17
|
+
const symbol = Object.getOwnPropertySymbols(testInstance).find((s) => s.description === "testType");
|
|
18
|
+
const impl = symbol ? testInstance[symbol] : undefined;
|
|
19
|
+
if (!isTestTypeImpl(impl)) {
|
|
20
|
+
throw new Error("@gooddata/sdk-e2e-utils: Playwright internals (TestTypeImpl._createTest/_modifier/_describe) " +
|
|
21
|
+
"were not found. They changed in this @playwright/test version; update " +
|
|
22
|
+
"src/create-test/playwright/playwright-internals.ts.");
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
createTest: (type, location, ...args) => impl._createTest(type, location, ...args),
|
|
26
|
+
modifier: (type, location, ...args) => impl._modifier(type, location, ...args),
|
|
27
|
+
describe: (type, location, ...args) => impl._describe(type, location, ...args),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Matches the location at the end of a stack line: "at fn (/path/file.ts:12:3)", "at /path/file.ts:12:3",
|
|
31
|
+
// "at fn (file:///path/file.ts:12:3)".
|
|
32
|
+
const STACK_FRAME_LOCATION = /\(?((?:file:\/\/)?[^\s()]+):(\d+):(\d+)\)?$/;
|
|
33
|
+
/**
|
|
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.
|
|
39
|
+
*
|
|
40
|
+
* Playwright installs source-map-support while loading test files, so the stack already shows
|
|
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
|
|
44
|
+
*/
|
|
45
|
+
export function getCallerLocation(wrapper) {
|
|
46
|
+
const target = {};
|
|
47
|
+
Error.captureStackTrace(target, wrapper);
|
|
48
|
+
for (const stackLine of (target.stack ?? "").split("\n").slice(1)) {
|
|
49
|
+
const match = STACK_FRAME_LOCATION.exec(stackLine.trim());
|
|
50
|
+
if (!match) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const file = match[1].startsWith("file://") ? fileURLToPath(match[1]) : match[1];
|
|
54
|
+
if (file.startsWith("node:")) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
return { file, line: Number(match[2]), column: Number(match[3]) };
|
|
58
|
+
}
|
|
59
|
+
throw new Error("@gooddata/sdk-e2e-utils: could not determine the caller location from the stack.");
|
|
60
|
+
}
|
|
@@ -1,42 +1,34 @@
|
|
|
1
|
-
import type { PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions,
|
|
1
|
+
import type { PlaywrightTestArgs, PlaywrightTestOptions, TestType as PlaywrightTestType, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo } from "@playwright/test";
|
|
2
2
|
/**
|
|
3
3
|
* @internal
|
|
4
4
|
*/
|
|
5
|
-
export type
|
|
5
|
+
export type PlaywrightBaseTestArgs = PlaywrightTestArgs & PlaywrightTestOptions;
|
|
6
6
|
/**
|
|
7
7
|
* @internal
|
|
8
8
|
*/
|
|
9
|
-
export type
|
|
9
|
+
export type PlaywrightBaseWorkerArgs = PlaywrightWorkerArgs & PlaywrightWorkerOptions;
|
|
10
10
|
/**
|
|
11
11
|
* @internal
|
|
12
12
|
*/
|
|
13
|
-
export type
|
|
13
|
+
export type { PlaywrightTestType };
|
|
14
14
|
/**
|
|
15
15
|
* @internal
|
|
16
16
|
*/
|
|
17
|
-
export type
|
|
17
|
+
export type PlaywrightTestInstance = PlaywrightTestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
|
|
18
18
|
/**
|
|
19
19
|
* @internal
|
|
20
20
|
*/
|
|
21
|
-
export type
|
|
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
|
|
27
|
+
export type PlaywrightTestBody<TestArgs> = (args: TestArgs, testInfo: TestInfo) => Promise<unknown> | unknown;
|
|
26
28
|
/**
|
|
27
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.
|
|
28
32
|
*/
|
|
29
|
-
export type
|
|
30
|
-
/**
|
|
31
|
-
* @internal
|
|
32
|
-
*/
|
|
33
|
-
export type DescribeSerial = typeof test.describe.serial;
|
|
34
|
-
/**
|
|
35
|
-
* @internal
|
|
36
|
-
*/
|
|
37
|
-
export type DescribeParallel = typeof test.describe.parallel;
|
|
38
|
-
/**
|
|
39
|
-
* @internal
|
|
40
|
-
*/
|
|
41
|
-
export type DescribeSkip = typeof test.describe.skip;
|
|
33
|
+
export type PlaywrightConditionBody<TestArgs> = (args: TestArgs) => boolean;
|
|
42
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":"
|
|
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
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TestDetails } from "@playwright/test";
|
|
2
|
+
import type { ITestDetails, TestConditionsValueConstraint } from "./types.js";
|
|
3
|
+
/** Strip custom fields before passing details to Playwright. */
|
|
4
|
+
export declare function toPlaywrightDetails(details: ITestDetails<TestConditionsValueConstraint>): TestDetails;
|
|
5
|
+
//# sourceMappingURL=to-playwright-details.d.ts.map
|
|
@@ -0,0 +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,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAE9E,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,6BAA6B,CAAC,GAAG,WAAW,CAIrG"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
/** Strip custom fields before passing details to Playwright. */
|
|
3
|
+
export function toPlaywrightDetails(details) {
|
|
4
|
+
// oxlint-disable-next-line @typescript-eslint/no-unused-vars
|
|
5
|
+
const { workspaceSettings: _ws, additionalWindowProperties: _awp, conditions: _c, ...rest } = details;
|
|
6
|
+
return Object.keys(rest).length > 0 ? rest : {};
|
|
7
|
+
}
|