@gravity-ui/playwright-tools 1.1.0 → 1.1.1

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.
@@ -1,8 +1,8 @@
1
1
  import type { MockNetworkFixtureBuilderParams } from './types';
2
- export type HarPatcherParams = Pick<MockNetworkFixtureBuilderParams, 'headersToRemove' | 'setCookieToRemove' | 'onHarEntryWillRead' | 'onHarEntryWillWrite' | 'onTransformHarLookupParams' | 'onTransformHarLookupResult'> & {
2
+ export type HarPatcherParams = Pick<MockNetworkFixtureBuilderParams, 'headersToRemove' | 'setCookieToRemove' | 'onHarEntryWillRead' | 'onHarEntryWillWrite' | 'onTransformHarLookupParams' | 'onTransformHarLookupResult' | 'shouldMarkIdenticalRequests'> & {
3
3
  /**
4
4
  * Base url of the test
5
5
  */
6
6
  baseURL: string;
7
7
  };
8
- export declare function harPatcher({ baseURL, headersToRemove: additionalHeadersToRemove, setCookieToRemove: additionalSetCookieToRemove, onHarEntryWillWrite, onHarEntryWillRead, onTransformHarLookupParams, onTransformHarLookupResult, }: HarPatcherParams): void;
8
+ export declare function harPatcher({ baseURL, headersToRemove: additionalHeadersToRemove, setCookieToRemove: additionalSetCookieToRemove, onHarEntryWillWrite, onHarEntryWillRead, onTransformHarLookupParams, onTransformHarLookupResult, shouldMarkIdenticalRequests, }: HarPatcherParams): void;
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.harPatcher = harPatcher;
4
4
  const har_1 = require("../../har");
5
+ const createDuplicateIdTransform_1 = require("../../utils/createDuplicateIdTransform");
6
+ const markIdenticalRequests_1 = require("../../utils/markIdenticalRequests");
5
7
  const DEFAULT_REMOVE_HEADERS = new Set([
6
8
  'cookie',
7
9
  'x-csrf-token',
@@ -10,7 +12,7 @@ const DEFAULT_REMOVE_HEADERS = new Set([
10
12
  ]);
11
13
  const DEFAULT_REMOVE_SET_COOKIE_FOR = new Set(['CSRF-TOKEN']);
12
14
  const baseUrlPLaceholder = 'https://base.url.placeholder';
13
- function harPatcher({ baseURL, headersToRemove: additionalHeadersToRemove = [], setCookieToRemove: additionalSetCookieToRemove = [], onHarEntryWillWrite, onHarEntryWillRead, onTransformHarLookupParams, onTransformHarLookupResult, }) {
15
+ function harPatcher({ baseURL, headersToRemove: additionalHeadersToRemove = [], setCookieToRemove: additionalSetCookieToRemove = [], onHarEntryWillWrite, onHarEntryWillRead, onTransformHarLookupParams, onTransformHarLookupResult, shouldMarkIdenticalRequests = false, }) {
14
16
  const headersToRemove = new Set([...DEFAULT_REMOVE_HEADERS, ...additionalHeadersToRemove]);
15
17
  const setCookieToRemove = new Set([
16
18
  ...DEFAULT_REMOVE_SET_COOKIE_FOR,
@@ -36,11 +38,30 @@ function harPatcher({ baseURL, headersToRemove: additionalHeadersToRemove = [],
36
38
  onHarEntryWillRead?.(entry, baseURL);
37
39
  }
38
40
  });
39
- (0, har_1.addHarLookupTransform)(onTransformHarLookupParams
40
- ? (params) => onTransformHarLookupParams(params, baseURL)
41
- : undefined, onTransformHarLookupResult
42
- ? (result, params) => onTransformHarLookupResult(result, params, baseURL)
43
- : undefined);
44
- // Filter out canceled requests before writing to har file
45
- (0, har_1.addFlushTransform)((entries) => entries.filter((entry) => entry.time !== -1));
41
+ // Create duplicate ID transformer once to preserve state between calls
42
+ const duplicateIdTransform = shouldMarkIdenticalRequests ? (0, createDuplicateIdTransform_1.createDuplicateIdTransform)() : null;
43
+ const onTransformHarLookupParamsFinal = (params) => {
44
+ // Apply duplicate ID transform first (if enabled)
45
+ const modifiedParams = duplicateIdTransform ? duplicateIdTransform(params) : params;
46
+ // Then apply custom transformer (if provided)
47
+ return onTransformHarLookupParams
48
+ ? onTransformHarLookupParams(modifiedParams, baseURL)
49
+ : modifiedParams;
50
+ };
51
+ const onTransformHarLookupResultFinal = (result, params) => {
52
+ return onTransformHarLookupResult
53
+ ? onTransformHarLookupResult(result, params, baseURL)
54
+ : result;
55
+ };
56
+ (0, har_1.addHarLookupTransform)(onTransformHarLookupParamsFinal, onTransformHarLookupResultFinal);
57
+ // Transform requests before writing to har file
58
+ (0, har_1.addFlushTransform)((entries) => {
59
+ // Before writing to har file, filter out canceled requests
60
+ const filteredEntries = entries.filter((entry) => entry.time !== -1);
61
+ // Add x-tests-duplicate-id header for identical requests (if enabled)
62
+ if (shouldMarkIdenticalRequests) {
63
+ return (0, markIdenticalRequests_1.markIdenticalRequests)(filteredEntries);
64
+ }
65
+ return filteredEntries;
66
+ });
46
67
  }
@@ -1,6 +1,6 @@
1
1
  import type { PlaywrightTestArgs, PlaywrightTestOptions, TestFixture } from '@playwright/test';
2
2
  import type { MockNetworkFixtureBuilderParams } from './types';
3
- export declare function mockNetworkFixtureBuilder<TestArgs extends PlaywrightTestArgs & PlaywrightTestOptions = PlaywrightTestArgs & PlaywrightTestOptions>({ shouldUpdate, forceUpdateIfHarMissing, updateTimeout, zip, url: urlMatcherBuilder, dumpsFilePath, enabled, ...harPatcherParams }: MockNetworkFixtureBuilderParams<TestArgs>): [TestFixture<boolean, TestArgs>, {
3
+ export declare function mockNetworkFixtureBuilder<TestArgs extends PlaywrightTestArgs & PlaywrightTestOptions = PlaywrightTestArgs & PlaywrightTestOptions>({ shouldUpdate, forceUpdateIfHarMissing, updateTimeout, zip, url, dumpsFilePath, optionallyEnabled, ...harPatcherParams }: MockNetworkFixtureBuilderParams): [TestFixture<boolean, TestArgs>, {
4
4
  auto: boolean;
5
5
  scope: "test";
6
6
  }];
@@ -3,39 +3,59 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mockNetworkFixtureBuilder = mockNetworkFixtureBuilder;
4
4
  const har_1 = require("../../har");
5
5
  const har_patcher_1 = require("./har-patcher");
6
- function mockNetworkFixtureBuilder({ shouldUpdate, forceUpdateIfHarMissing, updateTimeout, zip = true, url: urlMatcherBuilder, dumpsFilePath, enabled, ...harPatcherParams }) {
7
- const mockNetworkFixture = async (options, use, testInfo) => {
8
- const { baseURL: rawBaseURL, page } = options;
9
- if (enabled && !enabled(options)) {
10
- await use(false);
11
- return;
12
- }
13
- if (!rawBaseURL) {
14
- throw new Error('baseURL should be specified in playwright config');
15
- }
16
- const baseURL = rawBaseURL.replace(/\/+$/, '');
17
- (0, har_patcher_1.harPatcher)({
18
- baseURL,
19
- ...harPatcherParams,
20
- });
21
- const update = Boolean(shouldUpdate);
22
- const url = urlMatcherBuilder(baseURL);
23
- await (0, har_1.initDumps)(page, testInfo, {
24
- dumpsFilePath,
6
+ const fixtureFunction = async ({ baseURL: rawBaseURL, page, }, { shouldUpdate, forceUpdateIfHarMissing, updateTimeout, zip = true, url: urlMatcherBuilder, dumpsFilePath, ...harPatcherParams }, use, testInfo) => {
7
+ if (!rawBaseURL) {
8
+ throw new Error('baseURL should be specified in playwright config');
9
+ }
10
+ const baseURL = rawBaseURL.replace(/\/+$/, '');
11
+ (0, har_patcher_1.harPatcher)({
12
+ baseURL,
13
+ ...harPatcherParams,
14
+ });
15
+ const update = Boolean(shouldUpdate);
16
+ const url = urlMatcherBuilder(baseURL);
17
+ await (0, har_1.initDumps)(page, testInfo, {
18
+ dumpsFilePath,
19
+ forceUpdateIfHarMissing,
20
+ updateTimeout,
21
+ update,
22
+ url,
23
+ zip,
24
+ });
25
+ await use(!update);
26
+ };
27
+ function mockNetworkFixtureBuilder({ shouldUpdate, forceUpdateIfHarMissing, updateTimeout, zip = true, url, dumpsFilePath, optionallyEnabled, ...harPatcherParams }) {
28
+ const mockNetworkFixture = async ({ baseURL, page }, use, testInfo) => {
29
+ return fixtureFunction({ baseURL, page }, {
30
+ shouldUpdate,
25
31
  forceUpdateIfHarMissing,
26
32
  updateTimeout,
27
- update,
33
+ zip,
28
34
  url,
35
+ dumpsFilePath,
36
+ ...harPatcherParams,
37
+ }, use, testInfo);
38
+ };
39
+ const mockNetworkFixtureWithOptionalParam = async ({ baseURL, page, enableNetworkMocking }, use, testInfo) => {
40
+ if (!enableNetworkMocking) {
41
+ return use(false);
42
+ }
43
+ return fixtureFunction({ baseURL, page }, {
44
+ shouldUpdate,
45
+ forceUpdateIfHarMissing,
46
+ updateTimeout,
29
47
  zip,
30
- });
31
- await use(!update);
48
+ url,
49
+ dumpsFilePath,
50
+ ...harPatcherParams,
51
+ }, use, testInfo);
32
52
  };
33
53
  const fixtureOptions = {
34
54
  auto: true,
35
55
  scope: 'test',
36
56
  };
37
57
  const mockNetwork = [
38
- mockNetworkFixture,
58
+ optionallyEnabled ? mockNetworkFixtureWithOptionalParam : mockNetworkFixture,
39
59
  fixtureOptions,
40
60
  ];
41
61
  return mockNetwork;
@@ -1,6 +1,6 @@
1
- import type { PlaywrightTestArgs, PlaywrightTestOptions, TestInfo } from '@playwright/test';
1
+ import type { TestInfo } from '@playwright/test';
2
2
  import type { Entry, HarLookupParamsTransformFunction, HarLookupResultTransformFunction } from '../../har';
3
- export type MockNetworkFixtureBuilderParams<TestArgs extends PlaywrightTestArgs & PlaywrightTestOptions = PlaywrightTestArgs & PlaywrightTestOptions> = {
3
+ export type MockNetworkFixtureBuilderParams = {
4
4
  /**
5
5
  * Update dumps or not
6
6
  * @defaultValue `false`
@@ -78,9 +78,18 @@ export type MockNetworkFixtureBuilderParams<TestArgs extends PlaywrightTestArgs
78
78
  */
79
79
  onTransformHarLookupResult?: (result: Parameters<HarLookupResultTransformFunction>[0], params: Parameters<HarLookupResultTransformFunction>[1], baseURL: string) => ReturnType<HarLookupResultTransformFunction>;
80
80
  /**
81
- * Function to conditionally enable or disable the mock network fixture
82
- * @param options Test fixture arguments and options (includes custom test args if extended)
83
- * @returns `true` to enable the fixture, `false` to disable it
81
+ * Allow optionally enable fixture.
82
+ * Set "enableNetworkMocking" fixture to turn on/off network mocking
83
+ * @defaultValue false `
84
84
  */
85
- enabled?: (options: TestArgs) => boolean;
85
+ optionallyEnabled?: boolean;
86
+ /**
87
+ * Flag to enable or disable adding the x-tests-duplicate-id header for identical requests
88
+ * By default, the header is not added
89
+ * @defaultValue `false`
90
+ */
91
+ shouldMarkIdenticalRequests?: boolean;
92
+ };
93
+ export type OptionallyEnabledTestArgs = {
94
+ enableNetworkMocking?: boolean;
86
95
  };
package/har/index.d.ts CHANGED
@@ -10,5 +10,5 @@ export { clearHeaders } from './clearHeaders';
10
10
  export { initDumps } from './initDumps';
11
11
  export { replaceBaseUrlInEntry } from './replaceBaseUrlInEntry';
12
12
  export { setExtraHash } from './setExtraHash';
13
- export type { HARFile, Entry } from './types';
13
+ export type { HARFile, Entry, Header, LocalUtilsHarLookupParams, LocalUtilsHarLookupResult, QueryParameter, } from './types';
14
14
  export { defaultDumpsFilePathBuilder, dumpsPathBuldeWithSlugBuilder } from './dumpsFilePathBulders';
@@ -2,7 +2,7 @@ import type { Entry } from './types';
2
2
  /**
3
3
  * Replaces the base URL in the HAR file request entry
4
4
  *
5
- * @param entry ЗRecording from HAR file
5
+ * @param entry Recording from HAR file
6
6
  * @param fromUrl URL to replace
7
7
  * @param toUrl URL to replace with
8
8
  */
@@ -8,7 +8,7 @@ const escape_string_regexp_1 = __importDefault(require("escape-string-regexp"));
8
8
  /**
9
9
  * Replaces the base URL in the HAR file request entry
10
10
  *
11
- * @param entry ЗRecording from HAR file
11
+ * @param entry Recording from HAR file
12
12
  * @param fromUrl URL to replace
13
13
  * @param toUrl URL to replace with
14
14
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/playwright-tools",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Tools for Playwright Test",
5
5
  "keywords": [
6
6
  "playwright",
@@ -0,0 +1,11 @@
1
+ import type { LocalUtilsHarLookupParams } from '../har';
2
+ /**
3
+ * Creates a transformer for automatically adding the x-tests-duplicate-id header
4
+ * to requests when searching in a HAR file.
5
+ *
6
+ * This allows distinguishing identical requests without changing test code.
7
+ *
8
+ * The header is NOT added to the first request (for backward compatibility with old HARs),
9
+ * and is added starting from the second: 2, 3, 4...
10
+ */
11
+ export declare function createDuplicateIdTransform(): (params: LocalUtilsHarLookupParams) => LocalUtilsHarLookupParams;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDuplicateIdTransform = createDuplicateIdTransform;
4
+ const requestKeyService_1 = require("./requestKeyService");
5
+ /**
6
+ * Creates a transformer for automatically adding the x-tests-duplicate-id header
7
+ * to requests when searching in a HAR file.
8
+ *
9
+ * This allows distinguishing identical requests without changing test code.
10
+ *
11
+ * The header is NOT added to the first request (for backward compatibility with old HARs),
12
+ * and is added starting from the second: 2, 3, 4...
13
+ */
14
+ function createDuplicateIdTransform() {
15
+ const requestCounts = new Map();
16
+ return (params) => {
17
+ const key = (0, requestKeyService_1.createRequestKeyFromLookupParams)(params);
18
+ // Increment the counter for this request
19
+ const count = (requestCounts.get(key) || 0) + 1;
20
+ requestCounts.set(key, count);
21
+ // Add header only starting from the second call
22
+ // This ensures backward compatibility with old HAR files,
23
+ // where requests did not have the x-tests-duplicate-id header
24
+ if (count > 1) {
25
+ params.headers.push({
26
+ name: 'x-tests-duplicate-id',
27
+ value: String(count),
28
+ });
29
+ }
30
+ return params;
31
+ };
32
+ }
@@ -0,0 +1,9 @@
1
+ import type { Entry } from '../har';
2
+ /**
3
+ * Adds the x-tests-duplicate-id header only for duplicate requests.
4
+ *
5
+ * The first request remains without a header for backward compatibility with old HAR files.
6
+ * Starting from the second, requests receive headers: 2, 3, 4...
7
+ *
8
+ */
9
+ export declare const markIdenticalRequests: (entries: Entry[]) => Entry[];
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.markIdenticalRequests = void 0;
4
+ const requestKeyService_1 = require("./requestKeyService");
5
+ /**
6
+ * Adds the x-tests-duplicate-id header only for duplicate requests.
7
+ *
8
+ * The first request remains without a header for backward compatibility with old HAR files.
9
+ * Starting from the second, requests receive headers: 2, 3, 4...
10
+ *
11
+ */
12
+ const markIdenticalRequests = (entries) => {
13
+ const currentCounts = new Map();
14
+ entries.forEach((entry) => {
15
+ const key = (0, requestKeyService_1.createRequestKeyFromEntry)(entry);
16
+ const currentCount = (currentCounts.get(key) || 0) + 1;
17
+ currentCounts.set(key, currentCount);
18
+ // Add header only if this is not the first instance (currentCount > 1)
19
+ if (currentCount > 1) {
20
+ entry.request.headers.push({
21
+ name: 'x-tests-duplicate-id',
22
+ value: String(currentCount),
23
+ });
24
+ }
25
+ });
26
+ return entries;
27
+ };
28
+ exports.markIdenticalRequests = markIdenticalRequests;
@@ -0,0 +1,30 @@
1
+ import type { Entry, Header, LocalUtilsHarLookupParams, QueryParameter } from '../har';
2
+ /**
3
+ * Creates a unique request key based on its parameters.
4
+ * Used to identify identical requests when working with HAR files.
5
+ *
6
+ * The key is formed based on:
7
+ * - HTTP method
8
+ * - URL (without query parameters)
9
+ * - Query parameters (sorted)
10
+ * - Headers (sorted, excluding service headers)
11
+ */
12
+ export declare function createRequestKey(params: RequestKeyParams): string;
13
+ /**
14
+ * Parameters for creating a request key
15
+ */
16
+ type RequestKeyParams = {
17
+ method: string;
18
+ url: string;
19
+ headers: Header[];
20
+ queryString?: QueryParameter[];
21
+ };
22
+ /**
23
+ * Creates a key from Entry (when writing HAR)
24
+ */
25
+ export declare function createRequestKeyFromEntry(entry: Entry): string;
26
+ /**
27
+ * Creates a key from LocalUtilsHarLookupParams (when replaying)
28
+ */
29
+ export declare function createRequestKeyFromLookupParams(params: LocalUtilsHarLookupParams): string;
30
+ export {};
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRequestKey = createRequestKey;
4
+ exports.createRequestKeyFromEntry = createRequestKeyFromEntry;
5
+ exports.createRequestKeyFromLookupParams = createRequestKeyFromLookupParams;
6
+ /**
7
+ * Creates a unique request key based on its parameters.
8
+ * Used to identify identical requests when working with HAR files.
9
+ *
10
+ * The key is formed based on:
11
+ * - HTTP method
12
+ * - URL (without query parameters)
13
+ * - Query parameters (sorted)
14
+ * - Headers (sorted, excluding service headers)
15
+ */
16
+ function createRequestKey(params) {
17
+ const method = params.method;
18
+ const url = params.url;
19
+ // Parse URL to extract the base part and query parameters
20
+ const urlObj = new URL(url);
21
+ // Get sorted query parameters
22
+ const sortedQueryString = params.queryString
23
+ ? sortQueryStringFromArray(params.queryString)
24
+ : sortQueryStringFromUrl(urlObj);
25
+ // Get sorted headers (excluding service headers)
26
+ const sortedHeaders = sortHeaders(params.headers);
27
+ // Form the key: method + URL without query + query parameters + headers
28
+ return `${method}:${urlObj.origin}${urlObj.pathname}:${sortedQueryString}:${sortedHeaders}`;
29
+ }
30
+ /**
31
+ * Creates a key from Entry (when writing HAR)
32
+ */
33
+ function createRequestKeyFromEntry(entry) {
34
+ return createRequestKey({
35
+ method: entry.request.method,
36
+ url: entry.request.url,
37
+ headers: entry.request.headers,
38
+ queryString: entry.request.queryString,
39
+ });
40
+ }
41
+ /**
42
+ * Creates a key from LocalUtilsHarLookupParams (when replaying)
43
+ */
44
+ function createRequestKeyFromLookupParams(params) {
45
+ return createRequestKey({
46
+ method: params.method,
47
+ url: params.url,
48
+ headers: params.headers,
49
+ // queryString is automatically extracted from URL
50
+ });
51
+ }
52
+ /**
53
+ * Sorts query parameters from QueryParameter array
54
+ */
55
+ function sortQueryStringFromArray(queryString) {
56
+ return [...queryString]
57
+ .sort((a, b) => a.name.localeCompare(b.name))
58
+ .map((queryParameter) => `${queryParameter.name}=${queryParameter.value}`)
59
+ .join('&');
60
+ }
61
+ /**
62
+ * Sorts query parameters from URL
63
+ */
64
+ function sortQueryStringFromUrl(urlObj) {
65
+ return Array.from(urlObj.searchParams.entries())
66
+ .sort((a, b) => a[0].localeCompare(b[0]))
67
+ .map(([key, value]) => `${key}=${value}`)
68
+ .join('&');
69
+ }
70
+ /**
71
+ * Sorts headers, excluding service headers
72
+ */
73
+ function sortHeaders(headers) {
74
+ return [...headers]
75
+ .filter((header) => header.name.toLowerCase() !== 'x-tests-duplicate-id')
76
+ .sort((a, b) => a.name.localeCompare(b.name))
77
+ .map((header) => `${header.name.toLowerCase()}:${header.value}`)
78
+ .join('|');
79
+ }