Skip to content

Commit 5fd37e3

Browse files
committed
FI-943 feat: support CDP (aka native automation)
1 parent 1121f66 commit 5fd37e3

35 files changed

+441
-83
lines changed

autotests/actions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export {createRandomUser} from './createRandomUser';
22
export {setPageCookiesAndNavigateToUrl} from './setPageCookiesAndNavigateToUrl';
3+
export {setPageRequestHeadersAndNavigateToUrl} from './setPageRequestHeadersAndNavigateToUrl';

autotests/actions/setPageCookiesAndNavigateToUrl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {setHeadersAndNavigateToUrl} from 'e2ed/actions';
22
import {LogEventType} from 'e2ed/constants';
3-
import {log, replaceSetCookie} from 'e2ed/utils';
3+
import {getHeaderValue, log, replaceSetCookie} from 'e2ed/utils';
44

55
import type {Cookie, Headers, Url} from 'e2ed/types';
66

@@ -12,7 +12,7 @@ export const setPageCookiesAndNavigateToUrl = async (
1212
pageCookies: readonly Cookie[],
1313
): Promise<void> => {
1414
const mapResponseHeaders = (headers: Headers): Headers => {
15-
let setCookies = headers['set-cookie'];
15+
let setCookies = getHeaderValue(headers, 'set-cookie');
1616

1717
if (setCookies === undefined) {
1818
setCookies = [];
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {setHeadersAndNavigateToUrl} from 'e2ed/actions';
2+
import {LogEventType} from 'e2ed/constants';
3+
import {log} from 'e2ed/utils';
4+
5+
import type {Headers, Url} from 'e2ed/types';
6+
7+
/**
8+
* Navigate to the url and set additional page request headers.
9+
*/
10+
export const setPageRequestHeadersAndNavigateToUrl = async (
11+
url: Url,
12+
pageRequestHeaders: Headers,
13+
): Promise<void> => {
14+
const mapRequestHeaders = (): Headers => pageRequestHeaders;
15+
16+
log(
17+
`Navigate to ${url} and set page request headers`,
18+
{pageRequestHeaders, url},
19+
LogEventType.Action,
20+
);
21+
22+
await setHeadersAndNavigateToUrl(url, {mapRequestHeaders});
23+
};

autotests/bin/runDocker.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ docker run \
4545
--env E2ED_ORIGIN=$E2ED_ORIGIN \
4646
--env E2ED_DEBUG=$E2ED_DEBUG \
4747
--env __INTERNAL_E2ED_PATH_TO_PACK=$1 \
48+
--shm-size 512m \
4849
$DOCKER_IMAGE:$VERSION \
4950
& PID=$!
5051

autotests/context/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
export {clearPageCookies, getPageCookies, setPageCookies} from './pageCookies';
2+
export {
3+
clearPageRequestHeaders,
4+
getPageRequestHeaders,
5+
setPageRequestHeaders,
6+
} from './pageRequestHeaders';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {useContext} from 'e2ed';
2+
3+
import type {Headers} from 'e2ed/types';
4+
5+
/**
6+
* Get, set and clear additional page request headers, that will be added when navigating to the page.
7+
*/
8+
export const [getPageRequestHeaders, setPageRequestHeaders, clearPageRequestHeaders] =
9+
useContext<Headers>();

autotests/hooks/navigateTo.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import {setPageCookiesAndNavigateToUrl} from 'autotests/actions';
2-
import {clearPageCookies, getPageCookies} from 'autotests/context';
1+
import {
2+
setPageCookiesAndNavigateToUrl,
3+
setPageRequestHeadersAndNavigateToUrl,
4+
} from 'autotests/actions';
5+
import {
6+
clearPageCookies,
7+
clearPageRequestHeaders,
8+
getPageCookies,
9+
getPageRequestHeaders,
10+
} from 'autotests/context';
311
import {navigateToUrl} from 'e2ed/actions';
412

513
import type {NavigateTo} from 'autotests/types';
@@ -11,13 +19,17 @@ import type {NavigateTo} from 'autotests/types';
1119
*/
1220
export const navigateTo: NavigateTo = async (url) => {
1321
const pageCookies = getPageCookies();
22+
const pageRequestHeaders = getPageRequestHeaders();
1423

15-
if (pageCookies === undefined) {
16-
// As with all hooks, you can replace it with your own implementation.
17-
await navigateToUrl(url, {skipLogs: true});
18-
} else {
24+
if (pageCookies !== undefined) {
1925
clearPageCookies();
2026

2127
await setPageCookiesAndNavigateToUrl(url, pageCookies);
28+
} else if (pageRequestHeaders !== undefined) {
29+
clearPageRequestHeaders();
30+
31+
await setPageRequestHeadersAndNavigateToUrl(url, pageRequestHeaders);
32+
} else {
33+
await navigateToUrl(url, {skipLogs: true});
2234
}
2335
};

autotests/packs/allTests.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ import type {FilterTestsIntoPack, Pack} from 'autotests/types/packSpecific';
2323
const isLocalRun = runEnvironment === RunEnvironment.Local;
2424

2525
const browser = isLocalRun ? 'chrome:headless' : 'chromium:headless';
26-
const browserFlags = ['--disable-dev-shm-usage', '--disable-web-security'];
26+
const browserFlags = [
27+
'--disable-dev-shm-usage',
28+
'--disable-web-security',
29+
'--ignore-certificate-errors',
30+
];
2731

2832
const filterTestsIntoPack: FilterTestsIntoPack = ({options}) => options.meta.testId !== '13';
2933

autotests/pageObjects/pages/E2edReportExample.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import {setPageCookies} from 'autotests/context';
1+
import {setPageCookies, setPageRequestHeaders} from 'autotests/context';
22
import {E2edReportExample as E2edReportExampleRoute} from 'autotests/routes/pageRoutes';
33
import {locatorIdSelector} from 'autotests/selectors';
44
import {Page} from 'e2ed';
55
import {setReadonlyProperty} from 'e2ed/utils';
66

7-
import type {Cookie, Selector} from 'e2ed/types';
7+
import type {Cookie, Headers, Selector} from 'e2ed/types';
88

9-
type CustomPageParams = {pageCookies?: readonly Cookie[]} | undefined;
9+
type CustomPageParams = {pageCookies?: readonly Cookie[]; pageRequestHeaders?: Headers} | undefined;
1010

1111
/**
1212
* The e2ed report example page.
@@ -17,12 +17,18 @@ export class E2edReportExample extends Page<CustomPageParams> {
1717
*/
1818
readonly pageCookies!: readonly Cookie[];
1919

20+
/**
21+
* Request headers that we add to page request.
22+
*/
23+
readonly pageRequestHeaders: Headers | undefined;
24+
2025
override readonly pageStabilizationInterval = 600;
2126

2227
override init(this: E2edReportExample): void {
23-
const {pageCookies = []} = this.pageParams ?? {};
28+
const {pageCookies = [], pageRequestHeaders} = this.pageParams ?? {};
2429

2530
setReadonlyProperty(this, 'pageCookies', pageCookies);
31+
setReadonlyProperty(this, 'pageRequestHeaders', pageRequestHeaders);
2632
}
2733

2834
getRoute(): E2edReportExampleRoute {
@@ -52,11 +58,15 @@ export class E2edReportExample extends Page<CustomPageParams> {
5258
readonly testRunButton: Selector = this.testRunsList.findByLocatorId('app-retries-retry-button');
5359

5460
/**
55-
* Set page cookies to context before navigate.
61+
* Set page cookies and page request headers to context before navigate.
5662
*/
5763
override beforeNavigateToPage(): void {
5864
if (this.pageCookies.length !== 0) {
5965
setPageCookies(this.pageCookies);
6066
}
67+
68+
if (this.pageRequestHeaders !== undefined) {
69+
setPageRequestHeaders(this.pageRequestHeaders);
70+
}
6171
}
6272
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {test} from 'autotests';
2+
import {E2edReportExample} from 'autotests/pageObjects/pages';
3+
import {navigateToPage, waitForRequest} from 'e2ed/actions';
4+
5+
const headerName = 'x-custom-header';
6+
const pageRequestHeaders = {[headerName]: 'foo'};
7+
8+
test('set page request headers correctly', {meta: {testId: '17'}}, async () => {
9+
void navigateToPage(E2edReportExample, {pageRequestHeaders});
10+
11+
await waitForRequest(
12+
({requestHeaders}) => requestHeaders[headerName] === pageRequestHeaders[headerName],
13+
);
14+
});

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,10 @@
124124
"pretest:docker:copy": "rm -rf ./build/docker && mkdir --parents ./build/docker/node_modules/e2ed",
125125
"test:docker:copy": "cp -R ./build/autotests ./build/docker/autotests && rm -rf ./build/docker/autotests/reports",
126126
"posttest:docker:copy": "./bin/addPackageJsonToBuildDocker.sh",
127-
"test:docker": "(cd ./build/docker && E2ED_ORIGIN=https://google.com ./autotests/bin/runDocker.sh ./autotests/packs/allTests.ts)",
127+
"test:docker": "(cd ./build/docker && E2ED_ORIGIN=https://www.google.com ./autotests/bin/runDocker.sh ./autotests/packs/allTests.ts)",
128128
"test:dependencies": "node ./build/testDependencies.js",
129129
"test:esm": "node ./build/testEsmExports.mjs",
130-
"test:local": "(cd ./build && E2ED_ORIGIN=https://google.com ./node_modules/e2ed/bin/localEntrypoint.js ./autotests/packs/allTests.ts)",
130+
"test:local": "(cd ./build && E2ED_ORIGIN=https://www.google.com ./node_modules/e2ed/bin/localEntrypoint.js ./autotests/packs/allTests.ts)",
131131
"testcafe-hammerhead-up:publish": "./bin/forks/testcafe-hammerhead-up/publish.sh",
132132
"testcafe-hammerhead-up:clean": "./bin/forks/testcafe-hammerhead-up/clean.sh",
133133
"testcafe-hammerhead-up:test:install": "./bin/forks/testcafe-hammerhead-up/testInstall.sh",

src/context/waitForEventsState.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ export const getWaitForEventsState = (
3030

3131
const waitForEventsState: WaitForEventsState = {
3232
allRequestsCompletePredicates: new Set<AllRequestsCompletePredicateWithPromise>(),
33-
hashOfNotCompleteRequests: {},
33+
hashOfNotCompleteRequests: Object.create(
34+
null,
35+
) as WaitForEventsState['hashOfNotCompleteRequests'],
3436
hook: {} as RequestHookToWaitForEvents,
3537
requestPredicates: new Set<RequestPredicateWithPromise>(),
3638
responsePredicates: new Set<ResponsePredicateWithPromise>(),

src/testcaferc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const frozenPartOfTestCafeConfig: FrozenPartOfTestCafeConfig = {
3838
options: {esModuleInterop: true, resolveJsonModule: true},
3939
},
4040
},
41+
disableMultipleWindows: true,
4142
hostname: 'localhost',
4243
pageLoadTimeout: 0,
4344
reporter: [{name: 'for-e2ed'}],

src/types/config/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export type FrozenPartOfTestCafeConfig = DeepReadonly<{
3636
options?: {esModuleInterop?: boolean; resolveJsonModule?: boolean};
3737
};
3838
};
39+
disableMultipleWindows: boolean;
3940
hostname: string;
4041
pageLoadTimeout: number;
4142
reporter: readonly {name: string; output?: string}[];

src/types/global.d.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,33 @@ declare module 'testcafe-hammerhead-up/lib/processing/encoding/charset' {
7979
declare module 'testcafe-hammerhead-up/lib/request-pipeline/request-hooks/events/factory' {
8080
type RequestHookClassWithContext = import('./requestHooks').RequestHookClassWithContext;
8181

82-
const RequestPipelineRequestHookEventFactory: RequestHookClassWithContext;
82+
const RequestHookEventFactory: RequestHookClassWithContext;
8383

84-
export default RequestPipelineRequestHookEventFactory;
84+
export default RequestHookEventFactory;
85+
}
86+
87+
/**
88+
* Internal TestCafe module with request-hooks frame navigated events factory class for native automation.
89+
* @internal
90+
*/
91+
declare module 'testcafe-without-typecheck/lib/native-automation/request-hooks/event-factory/frame-navigated-event-based' {
92+
type RequestHookClassWithContext = import('./requestHooks').RequestHookClassWithContext;
93+
94+
const RequestHookEventFactory: RequestHookClassWithContext;
95+
96+
export default RequestHookEventFactory;
97+
}
98+
99+
/**
100+
* Internal TestCafe module with request-hooks events factory class for native automation.
101+
* @internal
102+
*/
103+
declare module 'testcafe-without-typecheck/lib/native-automation/request-hooks/event-factory/request-paused-event-based' {
104+
type RequestHookClassWithContext = import('./requestHooks').RequestHookClassWithContext;
105+
106+
const RequestHookEventFactory: RequestHookClassWithContext;
107+
108+
export default RequestHookEventFactory;
85109
}
86110

87111
/**

src/types/http/http.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import type {UtcTimeInMs} from '../date';
66
import type {CookieHeaderString, SetCookieHeaderString} from './cookie';
77
import type {StatusCode} from './statusCode';
88

9+
/**
10+
* Header entry from CDP.
11+
* {@link https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#type-HeaderEntry}
12+
*/
13+
export type HeaderEntry = Readonly<{name: string; value: string}>;
14+
915
/**
1016
* General type of arbitrary HTTP headers. All headers are in lower case.
1117
*/
@@ -20,7 +26,7 @@ export type Headers = Readonly<
2026
* Maps headers to new (overridden) headers.
2127
* All headers must be in lower case.
2228
*/
23-
export type MapHeaders = (headers: Headers) => Headers;
29+
export type MapHeaders = (this: void, headers: Headers) => Headers;
2430

2531
/**
2632
* Options for mappers of headers.

src/types/http/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export type {Cookie, CookieHeaderString, SameSite, SetCookieHeaderString} from './cookie';
22
export type {
3+
HeaderEntry,
34
Headers,
45
MapHeaders,
56
MapOptions,

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type {Fn, MergeFunctions} from './fn';
1111
export type {
1212
Cookie,
1313
CookieHeaderString,
14+
HeaderEntry,
1415
Headers,
1516
MapHeaders,
1617
MapOptions,

src/types/internal.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type {SafeHtml} from './html';
3535
export type {
3636
Cookie,
3737
CookieHeaderString,
38+
HeaderEntry,
3839
Headers,
3940
MapHeaders,
4041
MapOptions,
@@ -103,6 +104,7 @@ export type {
103104
TestRunButtonProps,
104105
} from './report';
105106
export type {
107+
HeadersModifiers,
106108
RequestHookConfigureResponseEvent,
107109
RequestHookContextId,
108110
RequestHookRequestEvent,

src/types/requestHooks.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {Brand} from './brand';
66
import type {Class} from './class';
77
import type {DeepReadonly} from './deep';
88
import type {Fn} from './fn';
9-
import type {Headers, StatusCode} from './http';
9+
import type {HeaderEntry, Headers, StatusCode} from './http';
1010

1111
/**
1212
* Maybe object with request hook context key.
@@ -15,22 +15,33 @@ import type {Headers, StatusCode} from './http';
1515
type MaybeWithContextKey = Partial<WithContextKey> | null | undefined;
1616

1717
/**
18-
* TestCafe internal request hook context (RequestPipelineContext).
19-
* Here we describe only the specific context fields used.
20-
* {@link https://github.com/DevExpress/testcafe-hammerhead/blob/master/src/request-pipeline/context/index.ts}
18+
* TestCafe internal request hook context (BaseRequestHookEventFactory).
19+
* Here we describe only the specific fields used.
20+
* Only the version without native automation has field `_ctx`,
21+
* and only the version with native automation (CPD) has field `_event`.
22+
* {@link https://github.com/DevExpress/testcafe-hammerhead/blob/master/src/request-pipeline/request-hooks/events/factory/index.ts}
23+
* {@link https://github.com/DevExpress/testcafe/blob/master/src/native-automation/request-hooks/event-factory/request-paused-event-based.ts}
24+
* {@link https://github.com/DevExpress/testcafe/blob/master/src/native-automation/request-hooks/event-factory/frame-navigated-event-based.ts}
2125
*/
22-
type RequestHookContext = DeepReadonly<{
26+
type RequestHookContext = Readonly<{
2327
[REQUEST_HOOK_CONTEXT_ID_KEY]?: RequestHookContextId;
24-
destRes: {
25-
headers?: Headers;
26-
};
28+
_ctx?: Readonly<{destRes?: Readonly<{headers?: Headers}>}>;
29+
_event?: Readonly<{requestId: string; responseHeaders?: readonly HeaderEntry[]}>;
2730
}>;
2831

2932
/**
3033
* Any object with request hook context key.
3134
*/
3235
type WithContextKey = {readonly [REQUEST_HOOK_CONTEXT_KEY]: RequestHookContext};
3336

37+
/**
38+
* Headers modification functions.
39+
*/
40+
export type HeadersModifiers = Readonly<{
41+
removeHeader(this: void, name: string): void;
42+
setHeader(this: void, name: string, value: string): void;
43+
}>;
44+
3445
/**
3546
* TestCafe charset class instance for encode/decode request/response body buffers.
3647
* @internal
@@ -44,7 +55,7 @@ export type RequestHookCharset = Brand<object, 'RequestHookCharset'>;
4455
*/
4556
export type RequestHookClassWithContext = Class<
4657
unknown[],
47-
Readonly<{_ctx: RequestHookContext} & Record<string, Fn<never[], MaybeWithContextKey>>>
58+
RequestHookContext & Readonly<Record<string, Fn<never[], MaybeWithContextKey>>>
4859
>;
4960

5061
/**
@@ -70,10 +81,7 @@ export type RequestHookRequestEvent = DeepReadonly<{
7081
*/
7182
export type RequestHookConfigureResponseEvent = DeepReadonly<
7283
WithContextKey & {
73-
_modifyResponseFunctions: {
74-
removeHeader(name: string): Promise<void>;
75-
setHeader(name: string, value: string): Promise<void>;
76-
};
84+
_modifyResponseFunctions: HeadersModifiers;
7785
}
7886
>;
7987

@@ -83,6 +91,7 @@ export type RequestHookConfigureResponseEvent = DeepReadonly<
8391
export type RequestHookResponseEvent = Readonly<{
8492
body?: Buffer;
8593
headers?: Headers;
94+
requestId?: string;
8695
statusCode?: StatusCode;
8796
}>;
8897

0 commit comments

Comments
 (0)