Skip to content

Commit

Permalink
FI-705 fix: stricter type of expect function
Browse files Browse the repository at this point in the history
fix: support usual promises in `expect` function
feat: add field `maxIntervalBetweenRequestsInMs` ot abstract class `Page`
feat: add examples of `mapBackendResponseErrorToLog`/`mapBackendResponseToLog`
tests: more tests of types of selectors methods
chore: update nodejs to current LTS (20.10.0)
chore: update alpine to 3.18.5
fix: support new contributor in `updateChangelog` script
refactor: move selectors code to `utils/selectors`
refactor: remove `utils/locators`
fix: use default cursor for empty expanded steps
feat: add duration to backend response logs
  • Loading branch information
uid11 committed Dec 5, 2023
1 parent dc29643 commit 60678f4
Show file tree
Hide file tree
Showing 64 changed files with 574 additions and 306 deletions.
11 changes: 9 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
FROM alpine:3.18.4
FROM node:20.10.0-alpine AS node

FROM alpine:3.18.5

COPY --from=node /usr/lib /usr/lib
COPY --from=node /usr/local/lib /usr/local/lib
COPY --from=node /usr/local/include /usr/local/include
COPY --from=node /usr/local/bin /usr/local/bin

RUN apk --no-cache upgrade && \
apk --no-cache add \
bash libevent nodejs npm chromium firefox xwininfo xvfb dbus eudev ttf-freefont fluxbox procps tzdata icu-data-full
bash libevent npm chromium firefox xwininfo xvfb dbus eudev ttf-freefont fluxbox procps tzdata icu-data-full

COPY ./build/node_modules/e2ed /node_modules/e2ed

Expand Down
2 changes: 2 additions & 0 deletions autotests/configurator/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export {doAfterPack} from './doAfterPack';
export {doBeforePack} from './doBeforePack';
export {mapBackendResponseErrorToLog} from './mapBackendResponseErrorToLog';
export {mapBackendResponseToLog} from './mapBackendResponseToLog';
export {mapLogPayloadInConsole} from './mapLogPayloadInConsole';
export {mapLogPayloadInLogFile} from './mapLogPayloadInLogFile';
export {mapLogPayloadInReport} from './mapLogPayloadInReport';
Expand Down
42 changes: 42 additions & 0 deletions autotests/configurator/mapBackendResponseErrorToLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {getDurationWithUnits} from 'e2ed/utils';

import type {MapBackendResponseErrorToLog} from 'autotests/types/packSpecific';

/**
* Maps responses with errors from the backend to "red" logs (as errors) during the test.
* It is assumed that the function will select responses with
* statuse codes of 400 and higher (client and server errors).
* Backend responses with errors are accumulated in separate "red" log step
* (with `logEventStatus: 'failed'`).
* Log the `responseBody` field carefully, as the body of backend response can be very large.
* If the function returns `undefined`, the response is not logged (skipped).
*/
export const mapBackendResponseErrorToLog: MapBackendResponseErrorToLog = ({
completionTimeInMs,
request,
responseBody,
responseHeaders,
statusCode,
}) => {
if (statusCode < 400) {
return undefined;
}

const {requestBody, utcTimeInMs, ...requestWithoutBody} = request ?? {};

const maybeWithDuration: {duration?: string} = {};

if (completionTimeInMs !== undefined && utcTimeInMs !== undefined) {
maybeWithDuration.duration = getDurationWithUnits(completionTimeInMs - utcTimeInMs);
}

return {
...maybeWithDuration,
request: {
requestBody: requestBody instanceof Buffer ? String(requestBody) : requestBody,
...requestWithoutBody,
},
responseBody: responseBody instanceof Buffer ? String(responseBody) : responseBody,
responseHeaders,
};
};
38 changes: 38 additions & 0 deletions autotests/configurator/mapBackendResponseToLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {getDurationWithUnits} from 'e2ed/utils';

import type {MapBackendResponseToLog} from 'autotests/types/packSpecific';

/**
* Maps responses from the backend to logs during the test.
* Backend responses received during a certain test step are accumulated
* in an array in the `backendResponses` field of the log of this step.
* Log the `responseBody` field carefully, as the body of backend response can be very large.
* If the function returns `undefined`, the response is not logged (skipped).
*/
export const mapBackendResponseToLog: MapBackendResponseToLog = ({
completionTimeInMs,
request,
responseBody,
responseHeaders,
statusCode,
}) => {
if (statusCode >= 400) {
return undefined;
}

if (request) {
const maybeWithDuration: {duration?: string} = {};

if (completionTimeInMs !== undefined && request.utcTimeInMs !== undefined) {
maybeWithDuration.duration = getDurationWithUnits(completionTimeInMs - request.utcTimeInMs);
}

return {...maybeWithDuration, statusCode, url: request?.url};
}

return {
responseBody: responseBody instanceof Buffer ? String(responseBody) : responseBody,
responseHeaders,
statusCode,
};
};
8 changes: 4 additions & 4 deletions autotests/packs/allTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {RunEnvironment, runEnvironment} from 'e2ed/configurator';
import {
doAfterPack,
doBeforePack,
mapBackendResponseErrorToLog,
mapBackendResponseToLog,
mapLogPayloadInConsole,
mapLogPayloadInLogFile,
mapLogPayloadInReport,
Expand Down Expand Up @@ -42,10 +44,8 @@ export const pack: Pack = {
filterTestsIntoPack,
liteReportFileName: 'lite-report.json',
logFileName: 'pack-logs.log',
mapBackendResponseErrorToLog: ({request, responseHeaders, statusCode}) =>
statusCode >= 400 ? {request, responseHeaders, statusCode} : undefined,
mapBackendResponseToLog: ({request, statusCode}) =>
statusCode < 400 ? {statusCode, url: request?.url} : undefined,
mapBackendResponseErrorToLog,
mapBackendResponseToLog,
mapLogPayloadInConsole,
mapLogPayloadInLogFile,
mapLogPayloadInReport,
Expand Down
20 changes: 13 additions & 7 deletions autotests/pageObjects/pages/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,19 @@ export class Main extends Page<CustomPageParams> {
}

override async waitForPageLoaded(): Promise<void> {
await waitForAllRequestsComplete(({url}) => {
if (url.startsWith('https://adservice.google.com/')) {
return false;
}

return true;
});
await waitForAllRequestsComplete(
({url}) => {
if (
url.startsWith('https://adservice.google.com/') ||
url.startsWith('https://play.google.com/')
) {
return false;
}

return true;
},
{maxIntervalBetweenRequestsInMs: this.maxIntervalBetweenRequestsInMs},
);

await waitForInterfaceStabilization(this.pageStabilizationInterval);
}
Expand Down
27 changes: 15 additions & 12 deletions autotests/pageObjects/pages/Search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,21 @@ export class Search extends MobilePage<CustomPageParams> {
}

override async waitForPageLoaded(): Promise<void> {
await waitForAllRequestsComplete(({url}) => {
if (
url.startsWith('https://adservice.google.com/') ||
url.startsWith('https://googleads.g.doubleclick.net/') ||
url.startsWith('https://play.google.com/') ||
url.startsWith('https://static.doubleclick.net/')
) {
return false;
}

return true;
});
await waitForAllRequestsComplete(
({url}) => {
if (
url.startsWith('https://adservice.google.com/') ||
url.startsWith('https://googleads.g.doubleclick.net/') ||
url.startsWith('https://play.google.com/') ||
url.startsWith('https://static.doubleclick.net/')
) {
return false;
}

return true;
},
{maxIntervalBetweenRequestsInMs: this.maxIntervalBetweenRequestsInMs},
);

await waitForInterfaceStabilization(this.pageStabilizationInterval);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import {it} from 'autotests';
import {E2edReportExample} from 'autotests/pageObjects/pages';
import {expect} from 'e2ed';
import {click, navigateToPage} from 'e2ed/actions';
import {getTimeoutPromise} from 'e2ed/utils';

it('custom selector methods', {meta: {testId: '15'}}, async () => {
it('selector custom methods', {meta: {testId: '15'}}, async () => {
const reportPage = await navigateToPage(E2edReportExample);

await expect(getTimeoutPromise(2_0000), 'should failed by timeout').ok();

await expect(reportPage.navigationRetries.exists, 'navigation retries exists').ok();

await expect(reportPage.navigationRetriesButton.exists, ' exists').ok();
Expand Down
57 changes: 31 additions & 26 deletions autotests/tests/internalTypeTests/selectors.skip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,30 @@ import {
locatorIdSelector,
} from 'autotests/selectors';

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

// @ts-expect-error: wrong number of arguments
htmlElementSelector.findByLocatorId();
// @ts-expect-error: wrong type of arguments
htmlElementSelector.findByLocatorId(0);
// ok
htmlElementSelector.findByLocatorId('id');
htmlElementSelector.findByLocatorId('id') satisfies Selector;
// ok
htmlElementSelector.findByLocatorId('id').findByLocatorId('id2');
htmlElementSelector.findByLocatorId('id').findByLocatorId('id2') satisfies Selector;
// ok
htmlElementSelector.findByLocatorId('id').find('.test-children');
htmlElementSelector.findByLocatorId('id').find('.test-children') satisfies Selector;
// ok
htmlElementSelector.find('body').findByLocatorId('id');
htmlElementSelector.find('body').findByLocatorId('id') satisfies Selector;

// ok
createSelector('id').findByLocatorId('id').find('body').findByLocatorId('id');
createSelector('id').findByLocatorId('id').find('body').findByLocatorId('id') satisfies Selector;
// ok
createSelectorByCss('id').findByLocatorId('id').find('body').findByLocatorId('id');
createSelectorByCss('id')
.findByLocatorId('id')
.find('body')
.findByLocatorId('id') satisfies Selector;
// ok
locatorIdSelector('id').findByLocatorId('id').find('body').findByLocatorId('id');
locatorIdSelector('id').findByLocatorId('id').find('body').findByLocatorId('id') satisfies Selector;

// @ts-expect-error: wrong number of arguments
locatorIdSelector();
Expand All @@ -32,41 +37,41 @@ locatorIdSelector();
locatorIdSelector(3);

// ok
htmlElementSelector.filterByLocatorId('id');
htmlElementSelector.filterByLocatorId('id') satisfies Selector;
// ok
htmlElementSelector.parentByLocatorId('id');
htmlElementSelector.parentByLocatorId('id') satisfies Selector;
// ok
htmlElementSelector.childByLocatorId('id');
htmlElementSelector.childByLocatorId('id') satisfies Selector;
// ok
htmlElementSelector.siblingByLocatorId('id');
htmlElementSelector.siblingByLocatorId('id') satisfies Selector;
// ok
htmlElementSelector.nextSiblingByLocatorId('id');
htmlElementSelector.nextSiblingByLocatorId('id') satisfies Selector;
// ok
htmlElementSelector.prevSiblingByLocatorId('id');
htmlElementSelector.prevSiblingByLocatorId('id') satisfies Selector;

// ok
htmlElementSelector.filterByLocatorParameter('prop', 'value');
htmlElementSelector.filterByLocatorParameter('prop', 'value') satisfies Selector;
// ok
htmlElementSelector.findByLocatorParameter('prop', 'value');
htmlElementSelector.findByLocatorParameter('prop', 'value') satisfies Selector;
// ok
htmlElementSelector.parentByLocatorParameter('prop', 'value');
htmlElementSelector.parentByLocatorParameter('prop', 'value') satisfies Selector;
// ok
htmlElementSelector.childByLocatorParameter('prop', 'value');
htmlElementSelector.childByLocatorParameter('prop', 'value') satisfies Selector;
// ok
htmlElementSelector.siblingByLocatorParameter('prop', 'value');
htmlElementSelector.siblingByLocatorParameter('prop', 'value') satisfies Selector;
// ok
htmlElementSelector.nextSiblingByLocatorParameter('prop', 'value');
htmlElementSelector.nextSiblingByLocatorParameter('prop', 'value') satisfies Selector;
// ok
htmlElementSelector.prevSiblingByLocatorParameter('prop', 'value');
htmlElementSelector.prevSiblingByLocatorParameter('prop', 'value') satisfies Selector;

// ok
void htmlElementSelector.getLocatorId();

// TODO: should be an error "wrong number of arguments"
void htmlElementSelector.getLocatorId('foo');

// ok
void htmlElementSelector.hasLocatorId();
// @ts-expect-error: TODO: should be ok
void htmlElementSelector.hasLocatorId() satisfies Promise<boolean>;

// TODO: should be an error "wrong number of arguments"
void htmlElementSelector.hasLocatorId('foo');
Expand All @@ -77,10 +82,10 @@ void htmlElementSelector.hasLocatorParameter();
// @ts-expect-error: wrong number of arguments
void htmlElementSelector.getLocatorParameter();

// ok
void htmlElementSelector.getLocatorParameter('prop');
// ok
void htmlElementSelector.hasLocatorParameter('prop');
// @ts-expect-error: TODO: should be ok
void htmlElementSelector.getLocatorParameter('prop') satisfies Promise<string | null>;
// @ts-expect-error: TODO: should be ok
void htmlElementSelector.hasLocatorParameter('prop') satisfies Promise<boolean>;

// ok
htmlElementSelector.getDescription() satisfies string | undefined;
12 changes: 8 additions & 4 deletions scripts/updateChangelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const SEPARATOR = '\n'.repeat(64);
const gitOptions = [
'log',
`HEAD...v${previousVersion}`,
`--pretty=tformat:%H%aN %s%n%b${'%n'.repeat(SEPARATOR.length)}`,
`--pretty="tformat:%H%aN|%s%n%b${'%n'.repeat(SEPARATOR.length)}"`,
];

const commits = execFileSync('git', gitOptions, EXEC_FILE_OPTIONS)
Expand All @@ -42,15 +42,19 @@ const markdownCommits = commits.map((commit) => {

assertValueIsDefined(firstLine);

const firstSpaceIndex = firstLine.indexOf(' ');
const subject = firstLine.slice(firstSpaceIndex + 1);
const authorNameIndex = firstLine.indexOf('|');
const subject = firstLine.slice(authorNameIndex + 1);

if (/^\d+\.\d+\.\d+$/.test(subject)) {
return '';
}

const hash = firstLine.slice(0, 40);
const authorName = firstLine.slice(40, firstSpaceIndex);
let authorName = firstLine.slice(40, authorNameIndex);

if (authorName.includes('Torchinskiy')) {
authorName = 'nnn3d';
}

const body = bodyLines.length === 0 ? '' : `\n\n ${bodyLines.join('\n\n ')}\n`;

Expand Down
21 changes: 19 additions & 2 deletions src/Page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,25 @@ export abstract class Page<PageParams = undefined> {

this.pageParams = pageParams as PageParams;

const {pageStabilizationInterval} = getFullPackConfig();
const {
pageStabilizationInterval,
waitForAllRequestsComplete: {maxIntervalBetweenRequestsInMs},
} = getFullPackConfig();

this.maxIntervalBetweenRequestsInMs = maxIntervalBetweenRequestsInMs;
this.pageStabilizationInterval = pageStabilizationInterval;
}

/**
* Default maximum interval (in milliseconds) between requests.
* After navigating to the page, `e2ed` will wait until
* all requests will complete, and only after that it will consider the page loaded.
* If there are no new requests for more than this interval,
* then we will consider that all requests completes
* The default value is taken from the corresponding field of the pack config.
*/
readonly maxIntervalBetweenRequestsInMs: number;

/**
* Immutable page parameters.
*/
Expand All @@ -40,6 +54,7 @@ export abstract class Page<PageParams = undefined> {
* After navigating to the page, `e2ed` will wait until
* the page is stable for the specified time in millisecond,
* and only after that it will consider the page loaded.
* The default value is taken from the corresponding field of the pack config.
*/
readonly pageStabilizationInterval: number;

Expand Down Expand Up @@ -80,7 +95,9 @@ export abstract class Page<PageParams = undefined> {
abstract getRoute(): PageRoute<unknown>;

async waitForPageLoaded(): Promise<void> {
await waitForAllRequestsComplete(() => true);
await waitForAllRequestsComplete(() => true, {
maxIntervalBetweenRequestsInMs: this.maxIntervalBetweenRequestsInMs,
});

await waitForInterfaceStabilization(this.pageStabilizationInterval);
}
Expand Down
Loading

0 comments on commit 60678f4

Please sign in to comment.