Skip to content

Commit

Permalink
Merge pull request #69 from joomcode/feat/add-createPageObjectsFromMu…
Browse files Browse the repository at this point in the history
…ltiLocator

feat: add `createPageObjectsFromMultiLocator` utility
  • Loading branch information
uid11 authored Mar 15, 2024
2 parents eb0ded0 + 70845cd commit 8a61c3e
Show file tree
Hide file tree
Showing 40 changed files with 490 additions and 167 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,12 @@ rules:
'@typescript-eslint/prefer-enum-initializers': error
'@typescript-eslint/prefer-find': error
'@typescript-eslint/prefer-for-of': error
'@typescript-eslint/quotes': [error, single, {avoidEscape: true}]
'@typescript-eslint/prefer-function-type': error
'@typescript-eslint/prefer-namespace-keyword': error
'@typescript-eslint/prefer-nullish-coalescing': error
'@typescript-eslint/prefer-optional-chain': error
'@typescript-eslint/prefer-readonly': error
'@typescript-eslint/quotes': [error, single, {avoidEscape: true}]
'@typescript-eslint/sort-type-constituents': [error, {checkIntersections: false}]
settings:
import/extensions: [.ts]
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ If the wait is longer than this timeout, then the promise returned by the `waitF
Required environment variables are defined in the `./autotests/variables.env` file (they cannot be deleted):

`E2ED_DOCKER_IMAGE`: the name of the docker image where the tests will run.
The image must be based on the e2ed base image.
The image must be based on the `e2ed` base image.

`E2ED_PATH_TO_TS_CONFIG_OF_PROJECT_FROM_ROOT`: the path to TypeScript config file of the project
from the root directory of the project. The project should have one common TypeScript config
Expand All @@ -415,9 +415,15 @@ You can pass the following optional environment variables to the `e2ed` process

`E2ED_ORIGIN`: origin-part of the url (`protocol` + `host`) on which the tests will be run. For example, `https://google.com`.

`E2ED_DEBUG`: run e2ed in `nodejs` debug mode (`--inspect-brk=0.0.0.0`) if this variable is not empty.
`E2ED_DEBUG`: run `e2ed` in `nodejs` debug mode (`--inspect-brk=0.0.0.0`) if this variable is not empty.

`E2ED_DOCKER_DEBUG_PORT`: debug port when run in docker (9229 by default).
`E2ED_DOCKER_DEBUG_PORT`: debug port when run in docker (`9229` by default).

`E2ED_TERMINATION_SIGNAL`: the termination signal received by the `e2ed` process (if any).
Typically this value is `'SIGUSR1'`.

`E2ED_TIMEOUT_FOR_GRACEFUL_SHUTDOWN_IN_SECONDS`: default timeout for "graceful shutdown" of `e2ed` process (in seconds).
If the variable is not set, the default value of `16` is used.

## License

Expand Down
14 changes: 11 additions & 3 deletions autotests/bin/runDocker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set +u
CONTAINER_LABEL="e2ed"
DEBUG_PORT="${E2ED_DOCKER_DEBUG_PORT:-9229}"
DIR="${E2ED_WORKDIR:-$PWD}"
E2ED_TIMEOUT_FOR_GRACEFUL_SHUTDOWN_IN_SECONDS=16
MOUNTDIR="${E2ED_MOUNTDIR:-$DIR}"
WITH_DEBUG=$([[ -z $E2ED_DEBUG ]] && echo "" || echo "--env E2ED_DEBUG=$DEBUG_PORT --publish $DEBUG_PORT:$DEBUG_PORT --publish $((DEBUG_PORT + 1)):$((DEBUG_PORT + 1))")
VERSION=$(grep -m1 \"e2ed\": $DIR/package.json | cut -d '"' -f 4)
Expand All @@ -26,10 +27,16 @@ onExit() {
then
echo "Docker container from image $E2ED_DOCKER_IMAGE:$VERSION already stopped"
else
sleep 18
echo "runDocker will sleep $(($E2ED_TIMEOUT_FOR_GRACEFUL_SHUTDOWN_IN_SECONDS + 2)) for seconds"
sleep "$(($E2ED_TIMEOUT_FOR_GRACEFUL_SHUTDOWN_IN_SECONDS + 2))"

echo "Stop docker container from image $E2ED_DOCKER_IMAGE:$VERSION"
docker stop --time=60 $CONTAINER_ID
CONTAINER_ID=$(docker ps --filter "label=$CONTAINER_LABEL" --format "{{.ID}}")

if [[ ! -z $CONTAINER_ID ]]
then
echo "Stop docker container from image $E2ED_DOCKER_IMAGE:$VERSION"
docker stop --time=60 $CONTAINER_ID
fi
fi

exit
Expand All @@ -41,6 +48,7 @@ trap "onExit" EXIT

docker run \
--env E2ED_ORIGIN=$E2ED_ORIGIN \
--env E2ED_TIMEOUT_FOR_GRACEFUL_SHUTDOWN_IN_SECONDS=$E2ED_TIMEOUT_FOR_GRACEFUL_SHUTDOWN_IN_SECONDS \
--env __INTERNAL_E2ED_PATH_TO_PACK=$1 \
--label $CONTAINER_LABEL \
--rm \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
} from 'autotests/actions';
import {setPageCookies, setPageRequestHeaders} from 'autotests/context';
import {E2edReportExample as E2edReportExampleRoute} from 'autotests/routes/pageRoutes';
import {locatorIdSelector} from 'autotests/selectors';
import {locatorIdSelector, rootLocator} from 'autotests/selectors';
import {Page} from 'e2ed';
import {setReadonlyProperty} from 'e2ed/utils';
import {createPageObjectsFromMultiLocator, setReadonlyProperty} from 'e2ed/utils';

import {TestRunButton} from './TestRunButton';

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

Expand Down Expand Up @@ -75,6 +77,17 @@ export class E2edReportExample extends Page<CustomPageParams> {
return new E2edReportExampleRoute();
}

/**
* Get `TestRunButton` hash (hashed by test `mainParams`).
*/
getTestRunButtons(): Promise<Readonly<Record<string, TestRunButton>>> {
return createPageObjectsFromMultiLocator({
PageObjectClass: TestRunButton,
keyParameter: 'runhash',
locator: rootLocator.retries.retry.button,
});
}

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

Expand Down
22 changes: 22 additions & 0 deletions autotests/pageObjects/pages/E2edReportExample/TestRunButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type {CreateLocator} from 'e2ed/createLocator';
import type {Selector, TestRunButtonLocator} from 'e2ed/types';

type Locator = CreateLocator<TestRunButtonLocator, Selector>;

/**
* `TestRun` button.
*/
export class TestRunButton {
readonly locator: Locator;

constructor(locator: Locator) {
this.locator = locator;
}

/**
* Element with `mainParams` of test.
*/
get parameters(): Selector {
return this.locator.parameters();
}
}
1 change: 1 addition & 0 deletions autotests/pageObjects/pages/E2edReportExample/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {E2edReportExample} from './E2edReportExample';
1 change: 1 addition & 0 deletions autotests/selectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export {inputSelector} from './inputSelector';
export {rootLocator} from './rootLocator';
export {
createSelector,
createSelectorByCss,
Expand Down
15 changes: 15 additions & 0 deletions autotests/selectors/rootLocator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {createSelectorByCss} from 'autotests/selectors';
import {createLocator, getCssSelectorFromAttributesChain} from 'e2ed/createLocator';

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

/**
* Project root locator, mapped to `Selector`.
*/
export const rootLocator = createLocator<ReportRootLocator, Selector>('app', {
mapAttributesChain: (attributesChain) => {
const cssSelector = getCssSelectorFromAttributesChain(attributesChain);

return createSelectorByCss(cssSelector.replace('data-test-runhash', 'data-runhash'));
},
});
13 changes: 11 additions & 2 deletions autotests/selectors/selectorFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import {createSelectorFunctions} from 'e2ed/selectors';
*/
export const {createSelector, createSelectorByCss, locatorIdSelector, htmlElementSelector} =
createSelectorFunctions({
getLocatorAttributeName: (parameter) =>
parameter === 'id' ? 'data-testid' : `data-test-${parameter}`,
getLocatorAttributeName: (parameter) => {
if (parameter === 'id') {
return 'data-testid';
}

if (parameter === 'runhash') {
return 'data-runhash';
}

return `data-test-${parameter}`;
},
});
28 changes: 25 additions & 3 deletions autotests/tests/e2edReportExample/selectorCustomMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,41 @@ test('selector custom methods', {meta: {testId: '15'}}, async () => {

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

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

await expect(
reportPage.navigationRetriesButtonSelected.exists,
'selected navigation retries button exists',
).ok();

const buttonsCount = await reportPage.navigationRetriesButton.count;
const testRunButtonsHash = await reportPage.getTestRunButtons();

const retriesButtonsCount = await reportPage.navigationRetriesButton.count;

const testRunButtonsCount = Object.keys(testRunButtonsHash).length;

await expect(reportPage.testRunButton.count, 'getTestRunButtons find all buttons').eql(
testRunButtonsCount,
);

let buttonsIndex = 0;

for (const testRunButton of Object.values(testRunButtonsHash)) {
const selector = reportPage.testRunButton.nth(buttonsIndex);
const mainParams = await selector.findByLocatorId(String(testRunButton.locator.parameters))
.textContent;

await expect(testRunButton.parameters.textContent, 'mainParams of test run button correct').eql(
mainParams,
);

buttonsIndex += 1;
}

await expect(
reportPage.navigationRetriesButtonSelected.getLocatorParameter('retry'),
'last navigation retries button selected',
).eql(String(buttonsCount));
).eql(String(retriesButtonsCount));

await expect(
reportPage.navigationRetriesButtonSelected.hasLocatorParameter('disabled'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint-disable max-classes-per-file */

import {type CreateLocator, createLocator, type Locator, type Node} from 'e2ed/createLocator';
import {createPageObjectsFromMultiLocator} from 'e2ed/utils';

import type {IsEqual, Selector, Void} from 'e2ed/types';

type FooLocator = Locator<{qux: Void}, {parameter: 'baz'}>;

type RootLocator = Locator<{foo: Node<{bar: FooLocator}>}>;

const rootLocator = createLocator<RootLocator, Selector>('app', {
mapAttributesChain: () => ({}) as unknown as Selector,
});

const fooLocator = rootLocator.foo.bar;

type BarMappedLocator = CreateLocator<Locator<{qux: Void}>, Selector>;

true satisfies IsEqual<typeof fooLocator, CreateLocator<FooLocator, Selector>>;

class Foo {
readonly bar: string;

readonly locator: typeof fooLocator;

constructor(locator: typeof fooLocator) {
this.locator = locator;
this.bar = 'baz';
}
}

class Bar {
readonly locator: BarMappedLocator;

constructor(locator: BarMappedLocator) {
this.locator = locator;
}
}

declare const barLocator: BarMappedLocator;

void (async () => {
const map = await createPageObjectsFromMultiLocator({
PageObjectClass: Foo,
keyParameter: 'parameter',
locator: fooLocator,
});

const foo = map['bar'];

if (foo !== undefined) {
foo.bar satisfies string;
}

await createPageObjectsFromMultiLocator({
PageObjectClass: Foo,
// @ts-expect-error: wrong parameter name
keyParameter: 'foo',
locator: fooLocator,
});

await createPageObjectsFromMultiLocator({
// @ts-expect-error: wrong locator in class
PageObjectClass: Bar,
keyParameter: 'parameter',
locator: fooLocator,
});

await createPageObjectsFromMultiLocator({
PageObjectClass: Bar,
// @ts-expect-error: locator without parameters
keyParameter: '',
locator: barLocator,
});

return map;
})();
7 changes: 4 additions & 3 deletions bin/dockerEntrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -eo pipefail
set +u

restoreE2edPackage () {
restoreE2edPackage() {
if [[ -d "./node_modules/_e2ed" ]]
then
echo "Restore temporarily hiding locally installed e2ed package:"
Expand All @@ -13,10 +13,11 @@ restoreE2edPackage () {
onExit() {
if [[ $PID ]] && ps -p $PID > /dev/null
then
echo "$PID is running"
echo "PID $PID is running"
kill -USR1 $PID

sleep 16
echo "dockerEntrypoint will sleep ${E2ED_TIMEOUT_FOR_GRACEFUL_SHUTDOWN_IN_SECONDS:-16} for seconds"
sleep "${E2ED_TIMEOUT_FOR_GRACEFUL_SHUTDOWN_IN_SECONDS:-16}"
fi

restoreE2edPackage;
Expand Down
Loading

0 comments on commit 8a61c3e

Please sign in to comment.