Skip to content

Commit

Permalink
Merge pull request #93 from joomcode/feat/support-waitings-before-ret…
Browse files Browse the repository at this point in the history
…ries

feat: support waitings before retries
  • Loading branch information
uid11 authored Nov 3, 2024
2 parents a833611 + 33278fa commit c68c9c4
Show file tree
Hide file tree
Showing 50 changed files with 399 additions and 133 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ rules:
'@typescript-eslint/class-literal-property-style': error
'@typescript-eslint/consistent-generic-constructors': error
'@typescript-eslint/consistent-indexed-object-style': error
'@typescript-eslint/consistent-return': error
'@typescript-eslint/consistent-return': off
'@typescript-eslint/consistent-type-assertions':
[error, {assertionStyle: as, objectLiteralTypeAssertions: never}]
'@typescript-eslint/consistent-type-definitions': [error, type]
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ Install the latest version of `e2ed` in devDependencies with the exact version:
npm install e2ed --save-dev --save-exact
```

Install [Playwright](https://playwright.dev/) [browsers](https://playwright.dev/docs/browsers)
(only `Chromium` for now):

```sh
npx e2ed-install-browsers
```

### Initialize

Initialize `e2ed` in the project; this will add an `autotests` directory
Expand Down Expand Up @@ -369,6 +376,9 @@ This parameter can be overridden in the test-specific options.

`viewportWidth: number`: width of viewport of page in pixels.

`waitBeforeRetry: (options: Options) => number`: returns how many milliseconds `e2ed`
should wait before running test (for retries).

`waitForAllRequestsComplete.maxIntervalBetweenRequestsInMs: number`: default maximum interval
(in milliseconds) between requests for `waitForAllRequestsComplete` function.
If there are no new requests for more than this interval, then the promise
Expand Down
1 change: 1 addition & 0 deletions autotests/configurator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ export type {
SkipTests,
TestFunction,
TestMeta,
WaitBeforeRetry,
} from './types';
1 change: 0 additions & 1 deletion autotests/configurator/mapLogPayloadInConsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const mapLogPayloadInConsole: MapLogPayloadInConsole = (message, payload)

if (
message.startsWith('Caught an error when running tests in retry') ||
message.startsWith('Warning from TestCafe:') ||
message.startsWith('Usage:')
) {
return payload;
Expand Down
1 change: 1 addition & 0 deletions autotests/configurator/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type {
MapLogPayloadInReport,
Pack,
TestFunction,
WaitBeforeRetry,
} from './packSpecific';
export type {SkipTests} from './skipTests';
export type {TestMeta} from './testMeta';
1 change: 1 addition & 0 deletions autotests/configurator/types/packSpecific.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export type MapLogPayloadInConsole = PackSpecificTypes['MapLogPayloadInConsole']
export type MapLogPayloadInLogFile = PackSpecificTypes['MapLogPayloadInLogFile'];
export type MapLogPayloadInReport = PackSpecificTypes['MapLogPayloadInReport'];
export type TestFunction = PackSpecificTypes['TestFunction'];
export type WaitBeforeRetry = PackSpecificTypes['WaitBeforeRetry'];
export type {Pack};
1 change: 0 additions & 1 deletion autotests/fixtures/fullMocks/jKDXNUZ75U.json

This file was deleted.

1 change: 1 addition & 0 deletions autotests/fixtures/fullMocks/mr-iHTD7Lp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"/api/product/135865":[{"completionTimeInMs":1729850053899,"duration":"4ms","request":{"method":"POST","query":{"size":"13"},"requestBody":{"cookies":[],"input":17,"model":"samsung","version":"12"},"requestHeaders":{"accept":"*/*","accept-language":"en-US","content-type":"application/json; charset=UTF-8","referer":"https://joomcode.github.io/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.35 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.35","sec-ch-ua":"\"Chromium\";v=\"130\", \"HeadlessChrome\";v=\"130\", \"Not?A_Brand\";v=\"99\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Linux\""},"url":"https://reqres.in/api/product/135865?size=13","utcTimeInMs":1729850053895},"responseBody":{"id":135865,"method":"POST","output":"17","payload":{"id":"135865","cookies":[],"input":17,"model":"samsung","version":"12"},"query":{"size":"13"},"url":"https://reqres.in/api/product/135865?size=13"},"responseHeaders":{"content-type":"application/json; charset=UTF-8","content-length":"201"},"statusCode":200},{"completionTimeInMs":1729850054453,"duration":"4ms","request":{"method":"POST","query":{"size":"13"},"requestBody":{"cookies":[],"input":17,"model":"samsung","version":"12"},"requestHeaders":{"accept":"*/*","accept-language":"en-US","content-type":"application/json; charset=UTF-8","referer":"https://joomcode.github.io/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.35 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.35","sec-ch-ua":"\"Chromium\";v=\"130\", \"HeadlessChrome\";v=\"130\", \"Not?A_Brand\";v=\"99\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Linux\""},"url":"https://reqres.in/api/product/135865?size=13","utcTimeInMs":1729850054449},"responseBody":{"cookies":[],"input":17,"model":"samsung","version":"12","id":"816","createdAt":"2024-10-25T09:54:14.388Z"},"responseHeaders":{"reporting-endpoints":"heroku-nel=https://nel.heroku.com/reports?ts=1729850054&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=LZBdsiG2rgFml4T6awFYmhftTlXLyvdfcMob39wiK2I%3D","nel":"{\"report_to\":\"heroku-nel\",\"max_age\":3600,\"success_fraction\":0.005,\"failure_fraction\":0.05,\"response_headers\":[\"Via\"]}","cf-cache-status":"DYNAMIC","etag":"W/\"6c-uS8VtSQALUKVvQbVlIe2ZwBwLRE\"","report-to":"{\"group\":\"heroku-nel\",\"max_age\":3600,\"endpoints\":[{\"url\":\"https://nel.heroku.com/reports?ts=1729850054&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=LZBdsiG2rgFml4T6awFYmhftTlXLyvdfcMob39wiK2I%3D\"}]}","via":"1.1 vegur","cf-ray":"8d8152f7a8d43cff-CDG","access-control-allow-origin":"*","content-length":"108","date":"Fri, 25 Oct 2024 09:54:14 GMT","content-type":"application/json; charset=utf-8","x-powered-by":"Express","server":"cloudflare"},"statusCode":201}]}
7 changes: 5 additions & 2 deletions autotests/packs/allTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ const filterTestsIntoPack: FilterTestsIntoPack = ({options}) => options.meta.tes
const userAgent =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.35 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.35';

const packTimeoutInMinutes = 5;
const msInMinute = 60_000;
const packTimeoutInMinutes = 5;

const waitBeforeRetryTimeout = 1_000;

/**
* Pack of tests or tasks (pack configuration object).
Expand Down Expand Up @@ -78,10 +80,11 @@ export const pack: Pack = {
takeViewportScreenshotOnError: true,
testFileGlobs: ['**/autotests/tests/**/*.ts'],
testIdleTimeout: 8_000,
testTimeout: 60_000,
testTimeout: 15_000,
userAgent,
viewportHeight: 1080,
viewportWidth: 1920,
waitBeforeRetry: () => waitBeforeRetryTimeout,
waitForAllRequestsComplete: {
maxIntervalBetweenRequestsInMs: 500,
timeout: 30_000,
Expand Down
2 changes: 1 addition & 1 deletion autotests/routes/apiRoutes/AddUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const pathStart = '/api/users';
* Client API route for adding user-worker.
*/
export class AddUser extends ApiRoute<Params, ApiAddUserRequest, ApiAddUserResponse> {
static override getParamsFromUrl(url: Url): Params {
static override getParamsFromUrlOrThrow(url: Url): Params {
const urlObject = new URL(url);

assertValueIsTrue(
Expand Down
2 changes: 1 addition & 1 deletion autotests/routes/apiRoutes/CreateProduct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class CreateProduct extends ApiRoute<
ApiCreateProductRequest,
ApiCreateProductResponse
> {
static override getParamsFromUrl(url: Url): Params {
static override getParamsFromUrlOrThrow(url: Url): Params {
const urlObject = new URL(url);

assertValueIsTrue(
Expand Down
2 changes: 1 addition & 1 deletion autotests/routes/pageRoutes/Search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Params = Readonly<{searchQuery: string}> | undefined;
* Route of the Search page.
*/
export class Search extends PageRoute<Params> {
static override getParamsFromUrl(url: Url): Params {
static override getParamsFromUrlOrThrow(url: Url): Params {
const {pathname, searchParams} = new URL(url);

assertValueIsTrue(pathname === '/search', 'search route matches on url', {url});
Expand Down
4 changes: 2 additions & 2 deletions autotests/tests/internalTypeTests/mockApiRoute.skip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ void mockApiRoute(Main, apiMockFunction);
// @ts-expect-error: unmockApiRoute require API route as first argument
void unmockApiRoute(Main);

// @ts-expect-error: mockApiRoute require API route with static method getParamsFromUrl
// @ts-expect-error: mockApiRoute require API route with static method getParamsFromUrlOrThrow
void mockApiRoute(CreateDevice, apiMockFunction);

// @ts-expect-error: unmockApiRoute require API route with static method getParamsFromUrl
// @ts-expect-error: unmockApiRoute require API route with static method getParamsFromUrlOrThrow
void unmockApiRoute(CreateDevice);

// ok
Expand Down
4 changes: 2 additions & 2 deletions autotests/tests/internalTypeTests/waitForEvents.skip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ void waitForRequestToRoute(AddUser, ({delay}, {requestBody, url}) => {
request.requestBody.job === 'foo' && 'delay' in routeParams && routeParams.delay > 0,
);

// @ts-expect-error: waitForRequestToRoute does not accept routes without `getParamsFromUrl` method
// @ts-expect-error: waitForRequestToRoute does not accept routes without `getParamsFromUrlOrThrow` method
void waitForRequestToRoute(GetUser);

// ok
Expand All @@ -84,5 +84,5 @@ void waitForResponseToRoute(AddUser, ({delay}, {responseBody, request: {requestB
routeParams.delay > 0,
);

// @ts-expect-error: waitForResponseToRoute does not accept routes without `getParamsFromUrl` method
// @ts-expect-error: waitForResponseToRoute does not accept routes without `getParamsFromUrlOrThrow` method
void waitForResponseToRoute(GetUser);
4 changes: 2 additions & 2 deletions autotests/tests/main/exists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ test('exists', {meta: {testId: '1'}, testIdleTimeout: 10_000, testTimeout: 15_00
const searchPage = await assertPage(Search, {searchQuery});

/**
* Do not use the following pageParams and url (by getParamsFromUrl) checks in your code.
* Do not use the following pageParams and url (by getParamsFromUrlOrThrow) checks in your code.
* These are e2ed internal checks. Use `assertPage` instead.
*/
await expect(searchPage.pageParams, 'pageParams is correct after assertPage').eql({
Expand All @@ -86,7 +86,7 @@ test('exists', {meta: {testId: '1'}, testIdleTimeout: 10_000, testTimeout: 15_00

const url = await getDocumentUrl();

await expect(SearchRoute.getParamsFromUrl(url), 'page url has expected params').eql({
await expect(SearchRoute.getParamsFromUrlOrThrow(url), 'page url has expected params').eql({
searchQuery,
});

Expand Down
2 changes: 1 addition & 1 deletion autotests/tests/mockApiRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test(

const fetchUrl = `https://reqres.in/api/product/${productId}?size=${product.size}` as Url;

const productRouteParams = CreateProductRoute.getParamsFromUrl(fetchUrl);
const productRouteParams = CreateProductRoute.getParamsFromUrlOrThrow(fetchUrl);

const productRouteFromUrl = new CreateProductRoute(productRouteParams);

Expand Down
44 changes: 22 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "e2ed",
"version": "0.18.15",
"description": "E2E testing framework over TestCafe",
"description": "E2E testing framework over Playwright",
"keywords": [
"E2E",
"TestCafe",
"Playwright",
"testing"
],
"author": "uid11",
Expand All @@ -24,14 +24,14 @@
"url": "git+https://github.com/joomcode/e2ed.git"
},
"dependencies": {
"@playwright/test": "1.48.1",
"@playwright/test": "1.48.2",
"create-locator": "0.0.25",
"get-modules-graph": "0.0.9",
"globby": "11.1.0"
},
"devDependencies": {
"@playwright/browser-chromium": "1.48.1",
"@types/node": "22.7.6",
"@playwright/browser-chromium": "1.48.2",
"@types/node": "22.8.7",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.18.0",
"assert-modules-support-case-insensitive-fs": "1.0.1",
Expand Down
25 changes: 20 additions & 5 deletions scripts/writePrunedPackageJson.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
/**
* @file Generates and writes pruned lightweight package.json for npm package.
* @file Generates and writes pruned lightweight `package.json` for npm package.
*/

import {writeFileSync} from 'node:fs';
import {join} from 'node:path';

import originaPackageJson from '../package.json';
import originalPackageJson from '../package.json';

type OriginalPackageJson = typeof originalPackageJson;

type PrunedPackageJson = Omit<OriginalPackageJson, 'bin' | 'devDependencies' | 'scripts'> & {
bin: OriginalPackageJson['bin'] & {['e2ed-install-browsers']: string};
devDependencies: undefined;
scripts: undefined;
};

const prunedPackageJsonPath = join(__dirname, 'node_modules', 'e2ed', 'package.json');

const prunedPackageJson: Partial<typeof originaPackageJson> = {...originaPackageJson};
const playwrightVersion = originalPackageJson.dependencies['@playwright/test'];

delete prunedPackageJson.devDependencies;
delete prunedPackageJson.scripts;
const prunedPackageJson: PrunedPackageJson = {
...originalPackageJson,
bin: {
...originalPackageJson.bin,
'e2ed-install-browsers': `npm install --global @playwright/browser-chromium@${playwrightVersion}`,
},
devDependencies: undefined,
scripts: undefined,
};

const prunedPackageJsonText = JSON.stringify(prunedPackageJson, null, 2);

Expand Down
3 changes: 2 additions & 1 deletion src/Page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ export abstract class Page<PageParams = undefined> {
* Asserts that we are on the expected page by `isMatch` flage.
* `isMatch` equals `true`, if url matches the page with given parameters, and `false` otherwise.
*/
assertPage(isMatch: boolean): AsyncVoid {
assertPage(isMatch: boolean, documentUrl: Url): AsyncVoid {
assertValueIsTrue(isMatch, `the document url matches the page "${this.constructor.name}"`, {
documentUrl,
page: this,
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/Route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export abstract class Route<RouteParams> {
* Returns route params from the passed url.
* @throws {Error} If the route does not match on the url.
*/
static getParamsFromUrl?(url: Url, method?: Method): unknown;
static getParamsFromUrlOrThrow?(url: Url, method?: Method): unknown;

/**
* Returns the url of the route.
Expand Down
Loading

0 comments on commit c68c9c4

Please sign in to comment.