Skip to content

Commit

Permalink
FI-943 feat: support CDP (aka native automation)
Browse files Browse the repository at this point in the history
  • Loading branch information
uid11 committed Dec 26, 2023
1 parent 1121f66 commit 5fd37e3
Show file tree
Hide file tree
Showing 35 changed files with 441 additions and 83 deletions.
1 change: 1 addition & 0 deletions autotests/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export {createRandomUser} from './createRandomUser';
export {setPageCookiesAndNavigateToUrl} from './setPageCookiesAndNavigateToUrl';
export {setPageRequestHeadersAndNavigateToUrl} from './setPageRequestHeadersAndNavigateToUrl';
4 changes: 2 additions & 2 deletions autotests/actions/setPageCookiesAndNavigateToUrl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {setHeadersAndNavigateToUrl} from 'e2ed/actions';
import {LogEventType} from 'e2ed/constants';
import {log, replaceSetCookie} from 'e2ed/utils';
import {getHeaderValue, log, replaceSetCookie} from 'e2ed/utils';

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

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

if (setCookies === undefined) {
setCookies = [];
Expand Down
23 changes: 23 additions & 0 deletions autotests/actions/setPageRequestHeadersAndNavigateToUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {setHeadersAndNavigateToUrl} from 'e2ed/actions';
import {LogEventType} from 'e2ed/constants';
import {log} from 'e2ed/utils';

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

/**
* Navigate to the url and set additional page request headers.
*/
export const setPageRequestHeadersAndNavigateToUrl = async (
url: Url,
pageRequestHeaders: Headers,
): Promise<void> => {
const mapRequestHeaders = (): Headers => pageRequestHeaders;

log(
`Navigate to ${url} and set page request headers`,
{pageRequestHeaders, url},
LogEventType.Action,
);

await setHeadersAndNavigateToUrl(url, {mapRequestHeaders});
};
1 change: 1 addition & 0 deletions autotests/bin/runDocker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ docker run \
--env E2ED_ORIGIN=$E2ED_ORIGIN \
--env E2ED_DEBUG=$E2ED_DEBUG \
--env __INTERNAL_E2ED_PATH_TO_PACK=$1 \
--shm-size 512m \
$DOCKER_IMAGE:$VERSION \
& PID=$!

Expand Down
5 changes: 5 additions & 0 deletions autotests/context/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export {clearPageCookies, getPageCookies, setPageCookies} from './pageCookies';
export {
clearPageRequestHeaders,
getPageRequestHeaders,
setPageRequestHeaders,
} from './pageRequestHeaders';
9 changes: 9 additions & 0 deletions autotests/context/pageRequestHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {useContext} from 'e2ed';

import type {Headers} from 'e2ed/types';

/**
* Get, set and clear additional page request headers, that will be added when navigating to the page.
*/
export const [getPageRequestHeaders, setPageRequestHeaders, clearPageRequestHeaders] =
useContext<Headers>();
24 changes: 18 additions & 6 deletions autotests/hooks/navigateTo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import {setPageCookiesAndNavigateToUrl} from 'autotests/actions';
import {clearPageCookies, getPageCookies} from 'autotests/context';
import {
setPageCookiesAndNavigateToUrl,
setPageRequestHeadersAndNavigateToUrl,
} from 'autotests/actions';
import {
clearPageCookies,
clearPageRequestHeaders,
getPageCookies,
getPageRequestHeaders,
} from 'autotests/context';
import {navigateToUrl} from 'e2ed/actions';

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

if (pageCookies === undefined) {
// As with all hooks, you can replace it with your own implementation.
await navigateToUrl(url, {skipLogs: true});
} else {
if (pageCookies !== undefined) {
clearPageCookies();

await setPageCookiesAndNavigateToUrl(url, pageCookies);
} else if (pageRequestHeaders !== undefined) {
clearPageRequestHeaders();

await setPageRequestHeadersAndNavigateToUrl(url, pageRequestHeaders);
} else {
await navigateToUrl(url, {skipLogs: true});
}
};
6 changes: 5 additions & 1 deletion autotests/packs/allTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import type {FilterTestsIntoPack, Pack} from 'autotests/types/packSpecific';
const isLocalRun = runEnvironment === RunEnvironment.Local;

const browser = isLocalRun ? 'chrome:headless' : 'chromium:headless';
const browserFlags = ['--disable-dev-shm-usage', '--disable-web-security'];
const browserFlags = [
'--disable-dev-shm-usage',
'--disable-web-security',
'--ignore-certificate-errors',
];

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

Expand Down
20 changes: 15 additions & 5 deletions autotests/pageObjects/pages/E2edReportExample.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {setPageCookies} from 'autotests/context';
import {setPageCookies, setPageRequestHeaders} from 'autotests/context';
import {E2edReportExample as E2edReportExampleRoute} from 'autotests/routes/pageRoutes';
import {locatorIdSelector} from 'autotests/selectors';
import {Page} from 'e2ed';
import {setReadonlyProperty} from 'e2ed/utils';

import type {Cookie, Selector} from 'e2ed/types';
import type {Cookie, Headers, Selector} from 'e2ed/types';

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

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

/**
* Request headers that we add to page request.
*/
readonly pageRequestHeaders: Headers | undefined;

override readonly pageStabilizationInterval = 600;

override init(this: E2edReportExample): void {
const {pageCookies = []} = this.pageParams ?? {};
const {pageCookies = [], pageRequestHeaders} = this.pageParams ?? {};

setReadonlyProperty(this, 'pageCookies', pageCookies);
setReadonlyProperty(this, 'pageRequestHeaders', pageRequestHeaders);
}

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

/**
* Set page cookies to context before navigate.
* Set page cookies and page request headers to context before navigate.
*/
override beforeNavigateToPage(): void {
if (this.pageCookies.length !== 0) {
setPageCookies(this.pageCookies);
}

if (this.pageRequestHeaders !== undefined) {
setPageRequestHeaders(this.pageRequestHeaders);
}
}
}
14 changes: 14 additions & 0 deletions autotests/tests/e2edReportExample/setPageRequestHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {test} from 'autotests';
import {E2edReportExample} from 'autotests/pageObjects/pages';
import {navigateToPage, waitForRequest} from 'e2ed/actions';

const headerName = 'x-custom-header';
const pageRequestHeaders = {[headerName]: 'foo'};

test('set page request headers correctly', {meta: {testId: '17'}}, async () => {
void navigateToPage(E2edReportExample, {pageRequestHeaders});

await waitForRequest(
({requestHeaders}) => requestHeaders[headerName] === pageRequestHeaders[headerName],
);
});
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@
"pretest:docker:copy": "rm -rf ./build/docker && mkdir --parents ./build/docker/node_modules/e2ed",
"test:docker:copy": "cp -R ./build/autotests ./build/docker/autotests && rm -rf ./build/docker/autotests/reports",
"posttest:docker:copy": "./bin/addPackageJsonToBuildDocker.sh",
"test:docker": "(cd ./build/docker && E2ED_ORIGIN=https://google.com ./autotests/bin/runDocker.sh ./autotests/packs/allTests.ts)",
"test:docker": "(cd ./build/docker && E2ED_ORIGIN=https://www.google.com ./autotests/bin/runDocker.sh ./autotests/packs/allTests.ts)",
"test:dependencies": "node ./build/testDependencies.js",
"test:esm": "node ./build/testEsmExports.mjs",
"test:local": "(cd ./build && E2ED_ORIGIN=https://google.com ./node_modules/e2ed/bin/localEntrypoint.js ./autotests/packs/allTests.ts)",
"test:local": "(cd ./build && E2ED_ORIGIN=https://www.google.com ./node_modules/e2ed/bin/localEntrypoint.js ./autotests/packs/allTests.ts)",
"testcafe-hammerhead-up:publish": "./bin/forks/testcafe-hammerhead-up/publish.sh",
"testcafe-hammerhead-up:clean": "./bin/forks/testcafe-hammerhead-up/clean.sh",
"testcafe-hammerhead-up:test:install": "./bin/forks/testcafe-hammerhead-up/testInstall.sh",
Expand Down
4 changes: 3 additions & 1 deletion src/context/waitForEventsState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export const getWaitForEventsState = (

const waitForEventsState: WaitForEventsState = {
allRequestsCompletePredicates: new Set<AllRequestsCompletePredicateWithPromise>(),
hashOfNotCompleteRequests: {},
hashOfNotCompleteRequests: Object.create(
null,
) as WaitForEventsState['hashOfNotCompleteRequests'],
hook: {} as RequestHookToWaitForEvents,
requestPredicates: new Set<RequestPredicateWithPromise>(),
responsePredicates: new Set<ResponsePredicateWithPromise>(),
Expand Down
1 change: 1 addition & 0 deletions src/testcaferc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const frozenPartOfTestCafeConfig: FrozenPartOfTestCafeConfig = {
options: {esModuleInterop: true, resolveJsonModule: true},
},
},
disableMultipleWindows: true,
hostname: 'localhost',
pageLoadTimeout: 0,
reporter: [{name: 'for-e2ed'}],
Expand Down
1 change: 1 addition & 0 deletions src/types/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type FrozenPartOfTestCafeConfig = DeepReadonly<{
options?: {esModuleInterop?: boolean; resolveJsonModule?: boolean};
};
};
disableMultipleWindows: boolean;
hostname: string;
pageLoadTimeout: number;
reporter: readonly {name: string; output?: string}[];
Expand Down
28 changes: 26 additions & 2 deletions src/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,33 @@ declare module 'testcafe-hammerhead-up/lib/processing/encoding/charset' {
declare module 'testcafe-hammerhead-up/lib/request-pipeline/request-hooks/events/factory' {
type RequestHookClassWithContext = import('./requestHooks').RequestHookClassWithContext;

const RequestPipelineRequestHookEventFactory: RequestHookClassWithContext;
const RequestHookEventFactory: RequestHookClassWithContext;

export default RequestPipelineRequestHookEventFactory;
export default RequestHookEventFactory;
}

/**
* Internal TestCafe module with request-hooks frame navigated events factory class for native automation.
* @internal
*/
declare module 'testcafe-without-typecheck/lib/native-automation/request-hooks/event-factory/frame-navigated-event-based' {
type RequestHookClassWithContext = import('./requestHooks').RequestHookClassWithContext;

const RequestHookEventFactory: RequestHookClassWithContext;

export default RequestHookEventFactory;
}

/**
* Internal TestCafe module with request-hooks events factory class for native automation.
* @internal
*/
declare module 'testcafe-without-typecheck/lib/native-automation/request-hooks/event-factory/request-paused-event-based' {
type RequestHookClassWithContext = import('./requestHooks').RequestHookClassWithContext;

const RequestHookEventFactory: RequestHookClassWithContext;

export default RequestHookEventFactory;
}

/**
Expand Down
8 changes: 7 additions & 1 deletion src/types/http/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import type {UtcTimeInMs} from '../date';
import type {CookieHeaderString, SetCookieHeaderString} from './cookie';
import type {StatusCode} from './statusCode';

/**
* Header entry from CDP.
* {@link https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#type-HeaderEntry}
*/
export type HeaderEntry = Readonly<{name: string; value: string}>;

/**
* General type of arbitrary HTTP headers. All headers are in lower case.
*/
Expand All @@ -20,7 +26,7 @@ export type Headers = Readonly<
* Maps headers to new (overridden) headers.
* All headers must be in lower case.
*/
export type MapHeaders = (headers: Headers) => Headers;
export type MapHeaders = (this: void, headers: Headers) => Headers;

/**
* Options for mappers of headers.
Expand Down
1 change: 1 addition & 0 deletions src/types/http/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type {Cookie, CookieHeaderString, SameSite, SetCookieHeaderString} from './cookie';
export type {
HeaderEntry,
Headers,
MapHeaders,
MapOptions,
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type {Fn, MergeFunctions} from './fn';
export type {
Cookie,
CookieHeaderString,
HeaderEntry,
Headers,
MapHeaders,
MapOptions,
Expand Down
2 changes: 2 additions & 0 deletions src/types/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export type {SafeHtml} from './html';
export type {
Cookie,
CookieHeaderString,
HeaderEntry,
Headers,
MapHeaders,
MapOptions,
Expand Down Expand Up @@ -103,6 +104,7 @@ export type {
TestRunButtonProps,
} from './report';
export type {
HeadersModifiers,
RequestHookConfigureResponseEvent,
RequestHookContextId,
RequestHookRequestEvent,
Expand Down
35 changes: 22 additions & 13 deletions src/types/requestHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {Brand} from './brand';
import type {Class} from './class';
import type {DeepReadonly} from './deep';
import type {Fn} from './fn';
import type {Headers, StatusCode} from './http';
import type {HeaderEntry, Headers, StatusCode} from './http';

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

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

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

/**
* Headers modification functions.
*/
export type HeadersModifiers = Readonly<{
removeHeader(this: void, name: string): void;
setHeader(this: void, name: string, value: string): void;
}>;

/**
* TestCafe charset class instance for encode/decode request/response body buffers.
* @internal
Expand All @@ -44,7 +55,7 @@ export type RequestHookCharset = Brand<object, 'RequestHookCharset'>;
*/
export type RequestHookClassWithContext = Class<
unknown[],
Readonly<{_ctx: RequestHookContext} & Record<string, Fn<never[], MaybeWithContextKey>>>
RequestHookContext & Readonly<Record<string, Fn<never[], MaybeWithContextKey>>>
>;

/**
Expand All @@ -70,10 +81,7 @@ export type RequestHookRequestEvent = DeepReadonly<{
*/
export type RequestHookConfigureResponseEvent = DeepReadonly<
WithContextKey & {
_modifyResponseFunctions: {
removeHeader(name: string): Promise<void>;
setHeader(name: string, value: string): Promise<void>;
};
_modifyResponseFunctions: HeadersModifiers;
}
>;

Expand All @@ -83,6 +91,7 @@ export type RequestHookConfigureResponseEvent = DeepReadonly<
export type RequestHookResponseEvent = Readonly<{
body?: Buffer;
headers?: Headers;
requestId?: string;
statusCode?: StatusCode;
}>;

Expand Down
Loading

0 comments on commit 5fd37e3

Please sign in to comment.