Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
33 changes: 17 additions & 16 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,20 @@ Modules in the dependency graph should only import the modules above them:
33. `utils/promise`
34. `utils/resourceUsage`
35. `utils/fs`
36. `utils/getGlobalErrorHandler`
37. `utils/tests`
38. `utils/end`
39. `utils/pack`
40. `useContext`
41. `context`
42. `utils/step`
43. `utils/apiStatistics`
44. `utils/selectors`
45. `selectors`
46. `utils/log`
47. `step`
48. `utils/waitForEvents`
49. `utils/expect`
50. `expect`
51. ...
36. `utils/completedTestRuns`
37. `utils/getGlobalErrorHandler`
38. `utils/tests`
39. `utils/end`
40. `utils/pack`
41. `useContext`
42. `context`
43. `utils/step`
44. `utils/apiStatistics`
45. `utils/selectors`
46. `selectors`
47. `utils/log`
48. `step`
49. `utils/waitForEvents`
50. `utils/expect`
51. `expect`
52. ...
2 changes: 2 additions & 0 deletions src/constants/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export {
API_STATISTICS_PATH,
AUTOTESTS_DIRECTORY_PATH,
COMPILED_USERLAND_CONFIG_DIRECTORY,
COMPLETED_TEST_RUNS_PATH,
CONFIG_PATH,
DOT_ENV_PATH,
EVENTS_DIRECTORY_PATH,
Expand All @@ -61,6 +62,7 @@ export {
INSTALLED_E2ED_DIRECTORY_PATH,
INTERNAL_DIRECTORY_NAME,
INTERNAL_REPORTS_DIRECTORY_PATH,
NOT_INCLUDED_IN_PACK_TESTS_PATH,
REPORTS_DIRECTORY_PATH,
SCREENSHOTS_DIRECTORY_PATH,
START_INFO_PATH,
Expand Down
20 changes: 20 additions & 0 deletions src/constants/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ export const COMPILED_USERLAND_CONFIG_DIRECTORY = join(
'config',
) as DirectoryPathFromRoot;

/**
* Relative (from root) path to file with already completed test runs.
* @internal
*/
export const COMPLETED_TEST_RUNS_PATH = join(
TMP_DIRECTORY_PATH,
'completedTestRuns.txt',
) as FilePathFromRoot;

/**
* Relative (from root) path to `config` file,
* that plays the role of the internal Playwright config.
Expand Down Expand Up @@ -137,6 +146,17 @@ export const GLOBAL_WARNINGS_PATH = join(
'globalWarnings.txt',
) as FilePathFromRoot;

/**
* Relative (from root) path to text file with list of not included in pack tests.
* For each not included in pack test in this file, a relative path
* to the file of this test is saved in a separate line.
* @internal
*/
export const NOT_INCLUDED_IN_PACK_TESTS_PATH = join(
TMP_DIRECTORY_PATH,
'notIncludedInPackTests.txt',
) as FilePathFromRoot;

/**
* Relative (from root) path to directory with tests screenshots.
* @internal
Expand Down
2 changes: 1 addition & 1 deletion src/types/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export type {
TestStaticOptions,
} from './testRun';
/** @internal */
export type {FullTestRun, RunTest, Test, TestUnit} from './testRun';
export type {CompletedTestRun, FullTestRun, RunTest, Test, TestUnit} from './testRun';
export type {MergeTuples, TupleRest} from './tuples';
export type {
CloneWithoutUndefinedProperties,
Expand Down
9 changes: 9 additions & 0 deletions src/types/testRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ import type {TestFilePath} from './paths';
import type {StringForLogs} from './string';
import type {TestMetaPlaceholder} from './userland';

/**
* Completed test run object used by internal runtime mechanics.
* @internal
*/
export type CompletedTestRun<TestMeta = TestMetaPlaceholder> = Readonly<{
status: TestRunStatus | 'started';
}> &
TestStaticOptions<TestMeta>;

/**
* Full test run object result of userland hooks (like mainParams and runHash).
* @internal
Expand Down
12 changes: 12 additions & 0 deletions src/utils/completedTestRuns/getIsSuccessfulTestRun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {FAILED_TEST_RUN_STATUSES, TestRunStatus} from '../../constants/internal';

import type {CompletedTestRun} from '../../types/internal';

/**
* Returns `true`, if test run was successful, and `false` otherwise.
* @internal
*/
export const getIsSuccessfulTestRun = ({status}: CompletedTestRun): boolean =>
status !== 'started' &&
status !== TestRunStatus.Broken &&
!FAILED_TEST_RUN_STATUSES.includes(status);
34 changes: 34 additions & 0 deletions src/utils/completedTestRuns/getIsTestFilePathUniq.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {writeGlobalError} from '../fs';

import {getIsTestOptionsDifferent} from './getIsTestOptionsDifferent';

import type {CompletedTestRun, TestStaticOptions} from '../../types/internal';

/**
* Returns `true`, if new test file path is uniq in completed test runs,
* otherwise writes global error.
* @internal
*/
export const getIsTestFilePathUniq = async (
testStaticOptions: TestStaticOptions,
completedTestRuns: readonly CompletedTestRun[],
): Promise<boolean> => {
const {filePath, name, options} = testStaticOptions;

const testRunsWithFilePath = completedTestRuns.filter((run) => run.filePath === filePath);

for (const completedTestRun of testRunsWithFilePath) {
if (
name !== completedTestRun.name ||
getIsTestOptionsDifferent(options, completedTestRun.options)
) {
await writeGlobalError(
`The file "${filePath}" contains two different tests: "${completedTestRun.name}" (${JSON.stringify(completedTestRun.options)}) and "${name}" (${JSON.stringify(options)})`,
);

return false;
}
}

return true;
};
34 changes: 34 additions & 0 deletions src/utils/completedTestRuns/getIsTestNameUniq.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {writeGlobalError} from '../fs';

import {getIsTestOptionsDifferent} from './getIsTestOptionsDifferent';

import type {CompletedTestRun, TestStaticOptions} from '../../types/internal';

/**
* Returns `true`, if new test file has uniq name in completed test runs,
* otherwise writes global error.
* @internal
*/
export const getIsTestNameUniq = async (
testStaticOptions: TestStaticOptions,
completedTestRuns: readonly CompletedTestRun[],
): Promise<boolean> => {
const {filePath, name, options} = testStaticOptions;

const testRunsWithName = completedTestRuns.filter((run) => run.name === name);

for (const completedTestRun of testRunsWithName) {
if (
filePath !== completedTestRun.filePath ||
getIsTestOptionsDifferent(options, completedTestRun.options)
) {
await writeGlobalError(
`There are two different tests with the same name "${name}": "${completedTestRun.filePath}" (${JSON.stringify(completedTestRun.options)}) and "${filePath}" (${JSON.stringify(options)})`,
);

return false;
}
}

return true;
};
18 changes: 18 additions & 0 deletions src/utils/completedTestRuns/getIsTestOptionsDifferent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type {TestOptions} from '../../types/internal';

/**
* Returns `true`, if test options different, and `false` otherwise.
* @internal
*/
export const getIsTestOptionsDifferent = (
firstOptions: TestOptions,
secondOptions: TestOptions,
): boolean => {
const firstMeta = {...firstOptions.meta, skipReason: undefined};
const secondMeta = {...secondOptions.meta, skipReason: undefined};

return (
JSON.stringify({...firstOptions, meta: firstMeta}) !==
JSON.stringify({...secondOptions, meta: secondMeta})
);
};
23 changes: 23 additions & 0 deletions src/utils/completedTestRuns/getSuccessfulTestFilePaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {readCompletedTestRuns} from '../fs';

import {getIsSuccessfulTestRun} from './getIsSuccessfulTestRun';

import type {TestFilePath} from '../../types/internal';

/**
* Get array of test file paths of successful tests.
* @internal
*/
export const getSuccessfulTestFilePaths = async (): Promise<readonly TestFilePath[]> => {
const completedTestRuns = await readCompletedTestRuns();

const successfulTestFilePaths: TestFilePath[] = [];

for (const completedTestRun of completedTestRuns) {
if (getIsSuccessfulTestRun(completedTestRun)) {
successfulTestFilePaths.push(completedTestRun.filePath);
}
}

return successfulTestFilePaths;
};
8 changes: 8 additions & 0 deletions src/utils/completedTestRuns/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @internal */
export {getIsSuccessfulTestRun} from './getIsSuccessfulTestRun';
/** @internal */
export {getIsTestFilePathUniq} from './getIsTestFilePathUniq';
/** @internal */
export {getIsTestNameUniq} from './getIsTestNameUniq';
/** @internal */
export {getSuccessfulTestFilePaths} from './getSuccessfulTestFilePaths';
10 changes: 7 additions & 3 deletions src/utils/events/collectFullEventsData.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import {EndE2edReason} from '../../constants/internal';

import {endE2edReason as maybeEndE2edReason} from '../end';
import {readApiStatistics, readEventsFromFiles, readStartInfo} from '../fs';
import {getNotIncludedInPackTests} from '../notIncludedInPackTests';
import {
readApiStatistics,
readEventsFromFiles,
readNotIncludedInPackTests,
readStartInfo,
} from '../fs';

import type {FullEventsData, UtcTimeInMs} from '../../types/internal';

Expand All @@ -15,7 +19,7 @@ export const collectFullEventsData = async (): Promise<FullEventsData> => {
const endE2edReason = maybeEndE2edReason ?? EndE2edReason.Unknown;
const endTimeInMs = Date.now() as UtcTimeInMs;
const fullTestRuns = await readEventsFromFiles([]);
const notIncludedInPackTests = await getNotIncludedInPackTests();
const notIncludedInPackTests = await readNotIncludedInPackTests();
const startInfo = await readStartInfo();

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import {
FAILED_TEST_RUN_STATUSES,
MESSAGE_BACKGROUND_COLOR_BY_STATUS,
TEST_RUN_STATUS_SYMBOLS,
TestRunStatus,
} from '../../constants/internal';

import {generalLog} from './generalLog';
import {getMessageWithBackgroundColor} from './getMessageWithBackgroundColor';
import {addSuccessfulTestRun, getSuccessfulTestFilePaths} from './successfulTestRuns';
import {getSuccessfulTestFilePaths} from '../completedTestRuns';
import {generalLog, getMessageWithBackgroundColor} from '../generalLog';

import type {FullTestRun} from '../../types/internal';

Expand All @@ -18,10 +15,6 @@ import type {FullTestRun} from '../../types/internal';
export const logEndTestRunEvent = async (fullTestRun: FullTestRun): Promise<void> => {
const {filePath, mainParams, name, options, runError, runId, status} = fullTestRun;

if (status !== TestRunStatus.Broken && !FAILED_TEST_RUN_STATUSES.includes(status)) {
await addSuccessfulTestRun(filePath);
}

const messageBackgroundColor = MESSAGE_BACKGROUND_COLOR_BY_STATUS[status];
const messageSymbol = TEST_RUN_STATUS_SYMBOLS[status];
const messageText = `${messageSymbol} ${status} ${mainParams} ${name}`;
Expand Down
6 changes: 4 additions & 2 deletions src/utils/events/registerEndTestRunEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import {getPlaywrightPage} from '../../useContext';

import {cloneWithoutLogEvents} from '../clone';
import {getRunErrorFromError} from '../error';
import {writeApiStatistics, writeTestRunToJsonFile} from '../fs';
import {generalLog, logEndTestRunEvent, writeLogsToFile} from '../generalLog';
import {writeApiStatistics, writeCompletedTestRun, writeTestRunToJsonFile} from '../fs';
import {generalLog, writeLogsToFile} from '../generalLog';
import {setReadonlyProperty} from '../object';
import {getUserlandHooks} from '../userland';

import {calculateTestRunStatus} from './calculateTestRunStatus';
import {getRegroupedSteps} from './getRegroupedSteps';
import {getTestRunEvent} from './getTestRunEvent';
import {logEndTestRunEvent} from './logEndTestRunEvent';
import {writeFullMocksIfNeeded} from './writeFullMocksIfNeeded';

import type {EndTestRunEvent, FullTestRun, RunHash, TestRun} from '../../types/internal';
Expand Down Expand Up @@ -77,6 +78,7 @@ export const registerEndTestRunEvent = async (endTestRunEvent: EndTestRunEvent):

const fullTestRun: FullTestRun = {mainParams, runHash, ...testRun};

await writeCompletedTestRun({filePath, name, options, status});
await logEndTestRunEvent(fullTestRun);

const apiStatistics = getApiStatistics();
Expand Down
8 changes: 8 additions & 0 deletions src/utils/fs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export {getLastLogEventTimeInMs, writeLogEventTime} from './logIsoString';
/** @internal */
export {readApiStatistics} from './readApiStatistics';
/** @internal */
export {readCompletedTestRuns} from './readCompletedTestRuns';
/** @internal */
export {readEventFromFile} from './readEventFromFile';
/** @internal */
export {readEventsFromFiles} from './readEventsFromFiles';
Expand All @@ -17,17 +19,23 @@ export {readGlobalErrors} from './readGlobalErrors';
/** @internal */
export {readGlobalWarnings} from './readGlobalWarnings';
/** @internal */
export {readNotIncludedInPackTests} from './readNotIncludedInPackTests';
/** @internal */
export {readStartInfo} from './readStartInfo';
/** @internal */
export {removeDirectory} from './removeDirectory';
/** @internal */
export {writeApiStatistics} from './writeApiStatistics';
/** @internal */
export {writeCompletedTestRun} from './writeCompletedTestRun';
export {writeFile} from './writeFile';
/** @internal */
export {writeGlobalError} from './writeGlobalError';
/** @internal */
export {writeGlobalWarning} from './writeGlobalWarning';
/** @internal */
export {writeNotIncludedInPackTest} from './writeNotIncludedInPackTest';
/** @internal */
export {writeStartInfo} from './writeStartInfo';
/** @internal */
export {writeTestRunToJsonFile} from './writeTestRunToJsonFile';
18 changes: 18 additions & 0 deletions src/utils/fs/readCompletedTestRuns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {readFile} from 'node:fs/promises';

import {COMPLETED_TEST_RUNS_PATH, READ_FILE_OPTIONS} from '../../constants/internal';

import type {CompletedTestRun} from '../../types/internal';

/**
* Reads array of completed test runs from temporary directory.
* @internal
*/
export const readCompletedTestRuns = async (): Promise<readonly CompletedTestRun[]> => {
const completedTestRunsJsonString = await readFile(
COMPLETED_TEST_RUNS_PATH,
READ_FILE_OPTIONS,
).catch(() => '');

return JSON.parse(`[${completedTestRunsJsonString.slice(0, -2)}]`) as CompletedTestRun[];
};
2 changes: 1 addition & 1 deletion src/utils/fs/readGlobalErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {readFile} from 'node:fs/promises';
import {GLOBAL_ERRORS_PATH, READ_FILE_OPTIONS} from '../../constants/internal';

/**
* Reads global errors of run from directory.
* Reads global errors of run from temporary directory.
* @internal
*/
export const readGlobalErrors = async (): Promise<readonly string[]> => {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/fs/readGlobalWarnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {readFile} from 'node:fs/promises';
import {GLOBAL_WARNINGS_PATH, READ_FILE_OPTIONS} from '../../constants/internal';

/**
* Reads global warnings of run from directory.
* Reads global warnings of run from temporary directory.
* @internal
*/
export const readGlobalWarnings = async (): Promise<readonly string[]> => {
Expand Down
Loading
Loading