Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/types/checks.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/**
* This type checks that the type `true` is passed to it.
*/
export type Expect<T extends true> = T;
export type Expect<Type extends true> = Type;

/**
* Returns `true` if types are exactly equal and `false` otherwise.
* IsEqual<{foo: string}, {foo: string}> = true.
* IsEqual<{readonly foo: string}, {foo: string}> = false.
*/
export type IsEqual<X, Y> =
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
(<Type>() => Type extends X ? 1 : 2) extends <Type>() => Type extends Y ? 1 : 2 ? true : false;

/**
* Returns `true` if key is readonly in object and `false` otherwise.
Expand Down
36 changes: 17 additions & 19 deletions src/utils/expect/additionalMatchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,81 +5,79 @@ import {toMatchScreenshot} from './toMatchScreenshot';
import type {Expect} from './Expect';
import type {NonSelectorAdditionalMatchers, SelectorMatchers} from './types';

import {expect} from '@playwright/test';
import {expect as playwrightExpect} from '@playwright/test';

/**
* Addition matchers.
* @internal
*/
export const additionalMatchers: NonSelectorAdditionalMatchers<unknown> & SelectorMatchers = {
contains(this: Expect, expected) {
async contains(this: Expect, expected) {
const {actualValue, description} = this;

if (typeof actualValue === 'string' || Array.isArray(actualValue)) {
return Promise.resolve(expect(actualValue, description).toContain(expected));
return playwrightExpect(actualValue, description).toContain(expected);
}

return Promise.resolve(
expect(actualValue, description).toEqual(
expect.objectContaining(expected as Record<string, unknown>),
),
return playwrightExpect(actualValue, description).toEqual(
playwrightExpect.objectContaining(expected as Record<string, unknown>),
);
},
async eql(this: Expect, expected) {
const {actualValue, description} = this;

return expect(actualValue, description).toEqual(expected);
return playwrightExpect(actualValue, description).toEqual(expected);
},
async gt(this: Expect, expected) {
const {actualValue, description} = this;

return expect(actualValue, description).toBeGreaterThan(expected);
return playwrightExpect(actualValue, description).toBeGreaterThan(expected);
},
async gte(this: Expect, expected) {
const {actualValue, description} = this;

return expect(actualValue, description).toBeGreaterThanOrEqual(expected);
return playwrightExpect(actualValue, description).toBeGreaterThanOrEqual(expected);
},
async lt(this: Expect, expected) {
const {actualValue, description} = this;

return expect(actualValue, description).toBeLessThan(expected);
return playwrightExpect(actualValue, description).toBeLessThan(expected);
},
async lte(this: Expect, expected) {
const {actualValue, description} = this;

return expect(actualValue, description).toBeLessThanOrEqual(expected);
return playwrightExpect(actualValue, description).toBeLessThanOrEqual(expected);
},
async match(this: Expect, expected) {
const {actualValue, description} = this;

return expect(actualValue, description).toMatch(expected);
return playwrightExpect(actualValue, description).toMatch(expected);
},
async notContains(this: Expect, expected) {
const {actualValue, description} = this;

if (typeof actualValue === 'string' || Array.isArray(actualValue)) {
return expect(actualValue, description).not.toContain(expected);
return playwrightExpect(actualValue, description).not.toContain(expected);
}

return expect(actualValue, description).not.toEqual(
expect.objectContaining(expected as Record<string, unknown>),
return playwrightExpect(actualValue, description).not.toEqual(
playwrightExpect.objectContaining(expected as Record<string, unknown>),
);
},
async notEql(this: Expect, expected) {
const {actualValue, description} = this;

return expect(actualValue, description).not.toEqual(expected);
return playwrightExpect(actualValue, description).not.toEqual(expected);
},
async notOk(this: Expect) {
const {actualValue, description} = this;

return expect(actualValue, description).not.toBeTruthy();
return playwrightExpect(actualValue, description).not.toBeTruthy();
},
async ok(this: Expect) {
const {actualValue, description} = this;

return expect(actualValue, description).toBeTruthy();
return playwrightExpect(actualValue, description).toBeTruthy();
},

toMatchScreenshot(this: Expect, expectedScreenshotId, options = {}) {
Expand Down
4 changes: 2 additions & 2 deletions src/utils/expect/applyAdditionalMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {Fn, Selector, SelectorPropertyRetryData} from '../../types/internal

import type {Expect} from './Expect';

import {expect} from '@playwright/test';
import {expect as playwrightExpect} from '@playwright/test';

/**
* Apply additional matcher (with retrying, if needed).
Expand All @@ -25,7 +25,7 @@ export const applyAdditionalMatcher = (

let context: Expect;

return expect(() => {
return playwrightExpect(() => {
const {args: selectorArgs, property, selector} = selectorPropertyRetryData;

const actualValue =
Expand Down
4 changes: 2 additions & 2 deletions src/utils/expect/createExpectMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {Fn, SelectorPropertyRetryData} from '../../types/internal';
import type {Expect} from './Expect';
import type {AssertionFunction, ExpectMethod} from './types';

import {expect} from '@playwright/test';
import {expect as playwrightExpect} from '@playwright/test';

const additionalAssertionTimeoutInMs = 1_000;

Expand Down Expand Up @@ -66,7 +66,7 @@ export const createExpectMethod = (
});
}

const assertion = expect(value, ctx.description) as unknown as Record<
const assertion = playwrightExpect(value, ctx.description) as unknown as Record<
string,
Fn<unknown[], Promise<unknown>>
>;
Expand Down
4 changes: 2 additions & 2 deletions src/utils/expect/toMatchScreenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {FilePathFromRoot, Selector, ToMatchScreenshotOptions, Url} from '..

import type {Expect} from './Expect';

import {expect} from '@playwright/test';
import {expect as playwrightExpect} from '@playwright/test';

type AdditionalLogFields = {
actualScreenshotId: string | undefined;
Expand Down Expand Up @@ -99,7 +99,7 @@ export const toMatchScreenshot = async (
try {
const playwrightLocator = actualValue.getPlaywrightLocator();

await expect(playwrightLocator, description).toHaveScreenshot(screenshotFileName, {
await playwrightExpect(playwrightLocator, description).toHaveScreenshot(screenshotFileName, {
mask: mask.map((selector) => selector.getPlaywrightLocator()),
...restOptions,
});
Expand Down
4 changes: 2 additions & 2 deletions src/utils/report/client/addDomContentLoadedHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
* This client function should not use scope variables (except global functions).
* @internal
*/
export function addDomContentLoadedHandler(handler: () => void): void {
export const addDomContentLoadedHandler = (handler: () => void): void => {
if (document.readyState !== 'loading') {
handler();

return;
}

document.addEventListener('DOMContentLoaded', handler);
}
};
14 changes: 9 additions & 5 deletions src/utils/report/client/chooseTestRun.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-console */

import {assertValueIsDefined as clientAssertValueIsDefined} from './assertValueIsDefined';
import {
MaybeApiStatistics as clientMaybeApiStatistics,
Expand All @@ -19,11 +21,10 @@ declare const reportClientState: ReportClientState;
* @internal
*/
// eslint-disable-next-line max-statements
export function chooseTestRun(runHash: RunHash): void {
export const chooseTestRun = (runHash: RunHash): void => {
const {e2edRightColumnContainer} = reportClientState;

if (e2edRightColumnContainer === undefined) {
// eslint-disable-next-line no-console
if (!e2edRightColumnContainer) {
console.error(
'Cannot find right column container (id="e2edRightColumnContainer"). Probably page not yet completely loaded. Please try click again later',
);
Expand All @@ -45,6 +46,10 @@ export function chooseTestRun(runHash: RunHash): void {
e2edRightColumnContainer.firstElementChild as HTMLElement | null;

if (!previousTestRunDetailsElement) {
console.error(
'Cannot find first child element in right column container (id="e2edRightColumnContainer"). Probably page not yet completely loaded. Please try click again later',
);

return;
}

Expand Down Expand Up @@ -72,7 +77,6 @@ export function chooseTestRun(runHash: RunHash): void {
const fullTestRun = fullTestRuns.find((testRun) => testRun.runHash === runHash);

if (fullTestRun === undefined) {
// eslint-disable-next-line no-console
console.error(
`Cannot find test run with hash ${runHash} in JSON report data. Probably JSON report data for this test run not yet loaded. Please try click again later`,
);
Expand All @@ -88,4 +92,4 @@ export function chooseTestRun(runHash: RunHash): void {
const nextTestRunDetailsElement = e2edRightColumnContainer.firstElementChild as HTMLElement;

testRunDetailsElementsByHash[runHash] = nextTestRunDetailsElement;
}
};
4 changes: 2 additions & 2 deletions src/utils/report/client/createJsxRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const sanitizeHtml = clientSanitizeHtml;
* This client function should not use scope variables (except global functions).
* @internal
*/
export function createJsxRuntime(): JSX.Runtime {
export const createJsxRuntime = (): JSX.Runtime => {
const maxDepth = 8;

const createElement: JSX.CreateElement = (type, properties, ...children) => {
Expand Down Expand Up @@ -104,4 +104,4 @@ export function createJsxRuntime(): JSX.Runtime {
};

return {Fragment, createElement};
}
};
12 changes: 8 additions & 4 deletions src/utils/report/client/groupLogEvents.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {LogEventType} from '../../../constants/internal';
import {LogEventStatus, LogEventType} from '../../../constants/internal';

import type {LogEvent, LogEventWithChildren} from '../../../types/internal';

Expand All @@ -16,14 +16,18 @@ export const groupLogEvents = (logEvents: readonly LogEvent[]): readonly LogEven
LogEventType.InternalAssert,
];

const isTopLevelEvent = (logEvent: LogEvent): boolean =>
topLevelTypes.includes(logEvent.type) ||
logEvent.payload?.logEventStatus === LogEventStatus.Failed;

const result: LogEventWithChildren[] = [];

for (const logEvent of logEvents) {
const last = result.at(-1);
const newEvent: LogEventWithChildren = {children: [], ...logEvent};

if (topLevelTypes.includes(logEvent.type)) {
if (last && !topLevelTypes.includes(last.type)) {
if (isTopLevelEvent(logEvent)) {
if (last && !isTopLevelEvent(last)) {
const firstTopLevel: LogEventWithChildren = {
children: [...result],
message: 'Initialization',
Expand All @@ -38,7 +42,7 @@ export const groupLogEvents = (logEvents: readonly LogEvent[]): readonly LogEven
}

result.push(newEvent);
} else if (last && topLevelTypes.includes(last.type)) {
} else if (last && isTopLevelEvent(last)) {
(last.children as LogEventWithChildren[]).push(newEvent);
} else {
result.push(newEvent);
Expand Down
6 changes: 3 additions & 3 deletions src/utils/report/client/onDomContentLoad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ const readJsonReportData = clientReadJsonReportData;
* This client function should not use scope variables (except global functions).
* @internal
*/
export function onDomContentLoad(): void {
export const onDomContentLoad = (): void => {
const e2edRightColumnContainer = document.getElementById('e2edRightColumnContainer') ?? undefined;

if (e2edRightColumnContainer === undefined) {
if (!e2edRightColumnContainer) {
// eslint-disable-next-line no-console
console.error(
'Cannot find right column container (id="e2edRightColumnContainer") after DOMContentLoaded.',
Expand All @@ -32,4 +32,4 @@ export function onDomContentLoad(): void {
}

reportClientState.readJsonReportDataObservers.length = 0;
}
};
6 changes: 3 additions & 3 deletions src/utils/report/client/parseMarkdownLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ const sanitizeHtml = clientSanitizeHtml;
* This client function should not use scope variables (except global functions).
* @internal
*/
export function parseMarkdownLinks(
export const parseMarkdownLinks = (
stringParts: readonly string[],
...values: readonly unknown[]
): SafeHtml {
): SafeHtml => {
const sanitizedHtml = sanitizeHtml(stringParts, ...values);

const htmlWithLinks = sanitizedHtml.replace(
Expand All @@ -25,4 +25,4 @@ export function parseMarkdownLinks(
);

return createSafeHtmlWithoutSanitize`${htmlWithLinks}`;
}
};
4 changes: 2 additions & 2 deletions src/utils/report/client/readJsonReportData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ declare const reportClientState: ReportClientState;
* This client function should not use scope variables (except global functions).
* @internal
*/
export function readJsonReportData(areAllScriptsLoaded = false): void {
export const readJsonReportData = (areAllScriptsLoaded = false): void => {
const {lengthOfReadedJsonReportDataParts} = reportClientState;
const scripts = document.querySelectorAll('body > script.e2edJsonReportData');
const {length} = scripts;
Expand All @@ -40,4 +40,4 @@ export function readJsonReportData(areAllScriptsLoaded = false): void {
}

reportClientState.lengthOfReadedJsonReportDataParts = newLength;
}
};
4 changes: 2 additions & 2 deletions src/utils/report/client/readPartOfJsonReportData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type Options = Readonly<{
* This client function should not use scope variables (except global functions).
* @internal
*/
export function readPartOfJsonReportData({scriptToRead, shouldLogError}: Options): boolean {
export const readPartOfJsonReportData = ({scriptToRead, shouldLogError}: Options): boolean => {
try {
const data = JSON.parse(scriptToRead?.textContent ?? '') as ScriptJsonData;

Expand All @@ -32,4 +32,4 @@ export function readPartOfJsonReportData({scriptToRead, shouldLogError}: Options
}

return true;
}
};
6 changes: 4 additions & 2 deletions src/utils/report/client/render/Step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ type Props = Readonly<{
isEnd?: boolean;
logEvent: LogEventWithChildren;
nextLogEventTime: UtcTimeInMs;
open?: boolean;
}>;

/**
* Renders single step of test run.
* This base client function should not use scope variables (except other base functions).
* @internal
*/
export const Step: JSX.Component<Props> = ({isEnd = false, logEvent, nextLogEventTime}) => {
export const Step: JSX.Component<Props> = ({isEnd = false, logEvent, nextLogEventTime, open}) => {
const {children, message, payload, time, type} = logEvent;
const date = new Date(time).toISOString();
const isPayloadEmpty = !payload || Object.keys(payload).length === 0;
Expand All @@ -49,6 +50,7 @@ export const Step: JSX.Component<Props> = ({isEnd = false, logEvent, nextLogEven
}

let content = <></>;
const isErrorScreenshot = pathToScreenshotOfPage !== undefined;

if (!isEnd) {
content =
Expand All @@ -60,7 +62,7 @@ export const Step: JSX.Component<Props> = ({isEnd = false, logEvent, nextLogEven
</span>
</div>
) : (
<details class="step__details">
<details class="step__details" open={open ?? isErrorScreenshot}>
<summary class="step__head">
<span class="step__name">{message}</span>
<span class="step__duration">
Expand Down
Loading
Loading