@toolstackhq/create-qa-patterns 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/index.js +282 -738
- package/lib/args.js +139 -0
- package/lib/constants.js +115 -0
- package/lib/interactive.js +131 -0
- package/lib/local-env.js +65 -0
- package/lib/metadata.js +329 -0
- package/lib/output.js +326 -0
- package/lib/prereqs.js +72 -0
- package/lib/scaffold.js +120 -0
- package/lib/templates.js +40 -0
- package/package.json +5 -3
- package/templates/cypress-template/.env.example +2 -2
- package/templates/cypress-template/.github/workflows/cypress-tests.yml +2 -2
- package/templates/cypress-template/README.md +29 -6
- package/templates/cypress-template/allurerc.mjs +1 -1
- package/templates/cypress-template/config/environments.ts +13 -11
- package/templates/cypress-template/config/runtime-config.ts +17 -12
- package/templates/cypress-template/config/secret-manager.ts +1 -1
- package/templates/cypress-template/config/test-env.ts +3 -3
- package/templates/cypress-template/cypress/e2e/ui-journey.cy.ts +12 -10
- package/templates/cypress-template/cypress/support/app-config.ts +5 -5
- package/templates/cypress-template/cypress/support/commands.ts +7 -7
- package/templates/cypress-template/cypress/support/data/data-factory.ts +6 -4
- package/templates/cypress-template/cypress/support/data/id-generator.ts +1 -1
- package/templates/cypress-template/cypress/support/data/seeded-faker.ts +2 -2
- package/templates/cypress-template/cypress/support/e2e.ts +2 -2
- package/templates/cypress-template/cypress/support/pages/login-page.ts +4 -4
- package/templates/cypress-template/cypress/support/pages/people-page.ts +10 -10
- package/templates/cypress-template/cypress.config.ts +9 -9
- package/templates/cypress-template/demo-apps/ui-demo-app/public/styles.css +1 -1
- package/templates/cypress-template/demo-apps/ui-demo-app/src/server.js +44 -41
- package/templates/cypress-template/demo-apps/ui-demo-app/src/store.js +31 -3
- package/templates/cypress-template/demo-apps/ui-demo-app/src/templates.js +5 -5
- package/templates/cypress-template/eslint.config.mjs +53 -45
- package/templates/cypress-template/package.json +6 -5
- package/templates/cypress-template/scripts/ensure-local-env.mjs +36 -0
- package/templates/cypress-template/scripts/generate-allure-report.mjs +16 -10
- package/templates/cypress-template/scripts/run-cypress.mjs +33 -24
- package/templates/cypress-template/scripts/run-tests.sh +1 -0
- package/templates/cypress-template/tsconfig.json +7 -1
- package/templates/playwright-template/.env.example +6 -6
- package/templates/playwright-template/.github/workflows/playwright-tests.yml +14 -5
- package/templates/playwright-template/README.md +25 -5
- package/templates/playwright-template/allurerc.mjs +1 -1
- package/templates/playwright-template/components/flash-message.ts +2 -2
- package/templates/playwright-template/config/environments.ts +16 -14
- package/templates/playwright-template/config/runtime-config.ts +17 -12
- package/templates/playwright-template/config/secret-manager.ts +1 -1
- package/templates/playwright-template/config/test-env.ts +3 -3
- package/templates/playwright-template/data/factories/data-factory.ts +6 -4
- package/templates/playwright-template/data/generators/id-generator.ts +1 -1
- package/templates/playwright-template/data/generators/seeded-faker.ts +2 -2
- package/templates/playwright-template/demo-apps/api-demo-server/src/server.js +9 -9
- package/templates/playwright-template/demo-apps/api-demo-server/src/store.js +1 -1
- package/templates/playwright-template/demo-apps/ui-demo-app/public/styles.css +1 -1
- package/templates/playwright-template/demo-apps/ui-demo-app/src/server.js +44 -41
- package/templates/playwright-template/demo-apps/ui-demo-app/src/store.js +31 -3
- package/templates/playwright-template/demo-apps/ui-demo-app/src/templates.js +5 -5
- package/templates/playwright-template/eslint.config.mjs +40 -40
- package/templates/playwright-template/fixtures/test-fixtures.ts +27 -12
- package/templates/playwright-template/lint/architecture-plugin.cjs +36 -31
- package/templates/playwright-template/package.json +7 -6
- package/templates/playwright-template/pages/base-page.ts +4 -4
- package/templates/playwright-template/pages/login-page.ts +9 -9
- package/templates/playwright-template/pages/people-page.ts +21 -17
- package/templates/playwright-template/playwright.config.ts +22 -19
- package/templates/playwright-template/reporters/structured-reporter.ts +11 -8
- package/templates/playwright-template/scripts/ensure-local-env.mjs +37 -0
- package/templates/playwright-template/scripts/generate-allure-report.mjs +16 -10
- package/templates/playwright-template/scripts/run-tests.sh +1 -0
- package/templates/playwright-template/tests/api-people.spec.ts +8 -6
- package/templates/playwright-template/tests/ui-journey.spec.ts +13 -8
- package/templates/playwright-template/tsconfig.json +3 -11
- package/templates/playwright-template/utils/logger.ts +12 -8
- package/templates/playwright-template/utils/test-step.ts +5 -5
- package/templates/wdio-template/.env.example +14 -0
- package/templates/wdio-template/.github/workflows/wdio-tests.yml +46 -0
- package/templates/wdio-template/README.md +241 -0
- package/templates/wdio-template/allurerc.mjs +10 -0
- package/templates/wdio-template/components/README.md +5 -0
- package/templates/wdio-template/components/flash-message.ts +16 -0
- package/templates/wdio-template/config/README.md +5 -0
- package/templates/wdio-template/config/environments.ts +40 -0
- package/templates/wdio-template/config/runtime-config.ts +53 -0
- package/templates/wdio-template/config/secret-manager.ts +29 -0
- package/templates/wdio-template/config/test-env.ts +9 -0
- package/templates/wdio-template/data/README.md +9 -0
- package/templates/wdio-template/data/factories/README.md +6 -0
- package/templates/wdio-template/data/factories/data-factory.ts +36 -0
- package/templates/wdio-template/data/generators/README.md +5 -0
- package/templates/wdio-template/data/generators/id-generator.ts +18 -0
- package/templates/wdio-template/data/generators/seeded-faker.ts +14 -0
- package/templates/wdio-template/demo-apps/ui-demo-app/public/styles.css +120 -0
- package/templates/wdio-template/demo-apps/ui-demo-app/src/server.js +152 -0
- package/templates/wdio-template/demo-apps/ui-demo-app/src/store.js +71 -0
- package/templates/wdio-template/demo-apps/ui-demo-app/src/templates.js +121 -0
- package/templates/wdio-template/eslint.config.mjs +86 -0
- package/templates/wdio-template/lint/architecture-plugin.cjs +123 -0
- package/templates/wdio-template/package-lock.json +11058 -0
- package/templates/wdio-template/package.json +44 -0
- package/templates/wdio-template/pages/README.md +6 -0
- package/templates/wdio-template/pages/base-page.ts +15 -0
- package/templates/wdio-template/pages/login-page.ts +27 -0
- package/templates/wdio-template/pages/people-page.ts +54 -0
- package/templates/wdio-template/reporters/README.md +5 -0
- package/templates/wdio-template/reporters/structured-reporter.ts +78 -0
- package/templates/wdio-template/scripts/README.md +5 -0
- package/templates/wdio-template/scripts/ensure-local-env.mjs +36 -0
- package/templates/wdio-template/scripts/generate-allure-report.mjs +72 -0
- package/templates/wdio-template/scripts/run-tests.sh +7 -0
- package/templates/wdio-template/scripts/run-wdio.mjs +114 -0
- package/templates/wdio-template/tests/README.md +7 -0
- package/templates/wdio-template/tests/ui-journey.spec.ts +52 -0
- package/templates/wdio-template/tsconfig.json +22 -0
- package/templates/wdio-template/utils/README.md +5 -0
- package/templates/wdio-template/utils/logger.ts +60 -0
- package/templates/wdio-template/utils/test-step.ts +20 -0
- package/templates/wdio-template/wdio.conf.ts +58 -0
- package/tests/args.test.js +58 -0
- package/tests/local-env.test.js +70 -0
- package/tests/metadata.test.js +147 -0
- package/tests/templates.test.js +44 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Page object for the login screen and its user actions.
|
|
2
|
-
import type { Page } from
|
|
2
|
+
import type { Page } from '@playwright/test';
|
|
3
3
|
|
|
4
|
-
import type { Logger } from
|
|
5
|
-
import { BasePage } from
|
|
4
|
+
import type { Logger } from '../utils/logger';
|
|
5
|
+
import { BasePage } from './base-page';
|
|
6
6
|
|
|
7
7
|
export class LoginPage extends BasePage {
|
|
8
8
|
constructor(page: Page, baseUrl: string, logger: Logger) {
|
|
@@ -10,14 +10,14 @@ export class LoginPage extends BasePage {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
async goto(): Promise<void> {
|
|
13
|
-
this.logger.info(
|
|
14
|
-
await this.page.goto(this.buildUrl(
|
|
13
|
+
this.logger.info('page.goto', { page: 'login' });
|
|
14
|
+
await this.page.goto(this.buildUrl('/login'));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
async login(username: string, password: string): Promise<void> {
|
|
18
|
-
this.logger.info(
|
|
19
|
-
await this.page.getByLabel(
|
|
20
|
-
await this.page.getByLabel(
|
|
21
|
-
await this.page.getByRole(
|
|
18
|
+
this.logger.info('login.submit', { username });
|
|
19
|
+
await this.page.getByLabel('Username').fill(username);
|
|
20
|
+
await this.page.getByLabel('Password').fill(password);
|
|
21
|
+
await this.page.getByRole('button', { name: 'Sign in' }).click();
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Page object for the people screen used in the starter UI workflow.
|
|
2
|
-
import type { Page } from
|
|
2
|
+
import type { Page } from '@playwright/test';
|
|
3
3
|
|
|
4
|
-
import type { PersonRecord } from
|
|
5
|
-
import type { Logger } from
|
|
6
|
-
import { BasePage } from
|
|
4
|
+
import type { PersonRecord } from '../data/factories/data-factory';
|
|
5
|
+
import type { Logger } from '../utils/logger';
|
|
6
|
+
import { BasePage } from './base-page';
|
|
7
7
|
|
|
8
8
|
export class PeoplePage extends BasePage {
|
|
9
9
|
constructor(page: Page, baseUrl: string, logger: Logger) {
|
|
@@ -11,30 +11,34 @@ export class PeoplePage extends BasePage {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async waitForReady(): Promise<void> {
|
|
14
|
-
await this.page
|
|
14
|
+
await this.page
|
|
15
|
+
.getByRole('heading', { level: 1, name: 'People', exact: true })
|
|
16
|
+
.waitFor();
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
async addPerson(person: PersonRecord): Promise<void> {
|
|
18
|
-
this.logger.info(
|
|
19
|
-
await this.page.getByLabel(
|
|
20
|
-
await this.page.getByLabel(
|
|
21
|
-
await this.page.getByLabel(
|
|
22
|
-
await this.page.getByLabel(
|
|
23
|
-
await this.page.getByRole(
|
|
24
|
-
await this.page.waitForLoadState(
|
|
20
|
+
this.logger.info('person.create', { personId: person.personId });
|
|
21
|
+
await this.page.getByLabel('Person ID').fill(person.personId);
|
|
22
|
+
await this.page.getByLabel('Name').fill(person.name);
|
|
23
|
+
await this.page.getByLabel('Role').fill(person.role);
|
|
24
|
+
await this.page.getByLabel('Email').fill(person.email);
|
|
25
|
+
await this.page.getByRole('button', { name: 'Add person' }).click();
|
|
26
|
+
await this.page.waitForLoadState('networkidle');
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
async getPersonSummary(
|
|
29
|
+
async getPersonSummary(
|
|
30
|
+
personId: string
|
|
31
|
+
): Promise<{ name: string; role: string; email: string } | null> {
|
|
28
32
|
const row = this.page.getByTestId(`person-row-${personId}`);
|
|
29
33
|
if (!(await row.count())) {
|
|
30
34
|
return null;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
const cells = row.getByRole(
|
|
37
|
+
const cells = row.getByRole('cell');
|
|
34
38
|
return {
|
|
35
|
-
name: (await cells.nth(0).textContent()) ??
|
|
36
|
-
role: (await cells.nth(1).textContent()) ??
|
|
37
|
-
email: (await cells.nth(2).textContent()) ??
|
|
39
|
+
name: (await cells.nth(0).textContent()) ?? '',
|
|
40
|
+
role: (await cells.nth(1).textContent()) ?? '',
|
|
41
|
+
email: (await cells.nth(2).textContent()) ?? ''
|
|
38
42
|
};
|
|
39
43
|
}
|
|
40
44
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// Central Playwright configuration for local runs, CI, reporters, and demo app startup.
|
|
2
|
-
import { defineConfig, devices } from
|
|
2
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
3
3
|
|
|
4
|
-
import { loadRuntimeConfig } from
|
|
4
|
+
import { loadRuntimeConfig } from './config/runtime-config';
|
|
5
5
|
|
|
6
6
|
const runtimeConfig = loadRuntimeConfig();
|
|
7
7
|
const shouldAutoStartDemoApps =
|
|
8
|
-
runtimeConfig.testEnv ===
|
|
9
|
-
runtimeConfig.uiBaseUrl ===
|
|
10
|
-
runtimeConfig.apiBaseUrl ===
|
|
11
|
-
process.env.PW_DISABLE_LOCAL_DEMO_APPS !==
|
|
8
|
+
runtimeConfig.testEnv === 'dev' &&
|
|
9
|
+
runtimeConfig.uiBaseUrl === 'http://127.0.0.1:3000' &&
|
|
10
|
+
runtimeConfig.apiBaseUrl === 'http://127.0.0.1:3001' &&
|
|
11
|
+
process.env.PW_DISABLE_LOCAL_DEMO_APPS !== 'true';
|
|
12
12
|
|
|
13
13
|
export default defineConfig({
|
|
14
|
-
testDir:
|
|
14
|
+
testDir: './tests',
|
|
15
15
|
fullyParallel: false,
|
|
16
16
|
forbidOnly: Boolean(process.env.CI),
|
|
17
17
|
retries: process.env.CI ? 1 : 0,
|
|
@@ -20,20 +20,23 @@ export default defineConfig({
|
|
|
20
20
|
expect: {
|
|
21
21
|
timeout: 10_000
|
|
22
22
|
},
|
|
23
|
-
outputDir:
|
|
23
|
+
outputDir: 'test-results',
|
|
24
24
|
reporter: [
|
|
25
|
-
[
|
|
26
|
-
[
|
|
25
|
+
['list'],
|
|
26
|
+
['html', { open: 'never', outputFolder: 'reports/html' }],
|
|
27
27
|
// Keep the HTML reporter as the default path most users expect.
|
|
28
28
|
// Remove the Allure line below if you prefer to stay with Playwright's built-in reporters only.
|
|
29
|
-
[
|
|
30
|
-
[
|
|
29
|
+
['allure-playwright', { resultsDir: 'allure-results' }],
|
|
30
|
+
[
|
|
31
|
+
'./reporters/structured-reporter.ts',
|
|
32
|
+
{ outputFile: 'reports/logs/playwright-events.jsonl' }
|
|
33
|
+
]
|
|
31
34
|
],
|
|
32
35
|
use: {
|
|
33
36
|
baseURL: runtimeConfig.uiBaseUrl,
|
|
34
|
-
trace:
|
|
35
|
-
screenshot:
|
|
36
|
-
video:
|
|
37
|
+
trace: 'retain-on-failure',
|
|
38
|
+
screenshot: 'only-on-failure',
|
|
39
|
+
video: 'retain-on-failure',
|
|
37
40
|
headless: !process.env.PWDEBUG
|
|
38
41
|
},
|
|
39
42
|
metadata: {
|
|
@@ -44,13 +47,13 @@ export default defineConfig({
|
|
|
44
47
|
webServer: shouldAutoStartDemoApps
|
|
45
48
|
? [
|
|
46
49
|
{
|
|
47
|
-
command:
|
|
50
|
+
command: 'npm run demo:ui',
|
|
48
51
|
url: `${runtimeConfig.uiBaseUrl}/health`,
|
|
49
52
|
reuseExistingServer: !process.env.CI,
|
|
50
53
|
timeout: 30_000
|
|
51
54
|
},
|
|
52
55
|
{
|
|
53
|
-
command:
|
|
56
|
+
command: 'npm run demo:api',
|
|
54
57
|
url: `${runtimeConfig.apiBaseUrl}/health`,
|
|
55
58
|
reuseExistingServer: !process.env.CI,
|
|
56
59
|
timeout: 30_000
|
|
@@ -59,9 +62,9 @@ export default defineConfig({
|
|
|
59
62
|
: undefined,
|
|
60
63
|
projects: [
|
|
61
64
|
{
|
|
62
|
-
name:
|
|
65
|
+
name: 'chromium',
|
|
63
66
|
use: {
|
|
64
|
-
...devices[
|
|
67
|
+
...devices['Desktop Chrome']
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Custom reporter that writes machine-readable test lifecycle events to disk.
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
6
|
FullConfig,
|
|
@@ -9,14 +9,17 @@ import type {
|
|
|
9
9
|
Suite,
|
|
10
10
|
TestCase,
|
|
11
11
|
TestResult
|
|
12
|
-
} from
|
|
12
|
+
} from '@playwright/test/reporter';
|
|
13
13
|
|
|
14
14
|
type ReporterOptions = {
|
|
15
15
|
outputFile?: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
class StructuredReporter implements Reporter {
|
|
19
|
-
private outputFile = path.resolve(
|
|
19
|
+
private outputFile = path.resolve(
|
|
20
|
+
process.cwd(),
|
|
21
|
+
'reports/logs/playwright-events.jsonl'
|
|
22
|
+
);
|
|
20
23
|
|
|
21
24
|
constructor(options?: ReporterOptions) {
|
|
22
25
|
if (options?.outputFile) {
|
|
@@ -27,7 +30,7 @@ class StructuredReporter implements Reporter {
|
|
|
27
30
|
|
|
28
31
|
onBegin(config: FullConfig, suite: Suite): void {
|
|
29
32
|
this.write({
|
|
30
|
-
event:
|
|
33
|
+
event: 'run.started',
|
|
31
34
|
projectCount: config.projects.length,
|
|
32
35
|
testCount: suite.allTests().length
|
|
33
36
|
});
|
|
@@ -35,7 +38,7 @@ class StructuredReporter implements Reporter {
|
|
|
35
38
|
|
|
36
39
|
onTestEnd(test: TestCase, result: TestResult): void {
|
|
37
40
|
this.write({
|
|
38
|
-
event:
|
|
41
|
+
event: 'test.finished',
|
|
39
42
|
title: test.title,
|
|
40
43
|
tags: test.title.match(/@\w+/g) ?? [],
|
|
41
44
|
status: result.status,
|
|
@@ -45,7 +48,7 @@ class StructuredReporter implements Reporter {
|
|
|
45
48
|
|
|
46
49
|
onEnd(result: FullResult): void {
|
|
47
50
|
this.write({
|
|
48
|
-
event:
|
|
51
|
+
event: 'run.finished',
|
|
49
52
|
status: result.status
|
|
50
53
|
});
|
|
51
54
|
}
|
|
@@ -54,7 +57,7 @@ class StructuredReporter implements Reporter {
|
|
|
54
57
|
fs.appendFileSync(
|
|
55
58
|
this.outputFile,
|
|
56
59
|
`${JSON.stringify({ timestamp: new Date().toISOString(), ...payload })}\n`,
|
|
57
|
-
|
|
60
|
+
'utf8'
|
|
58
61
|
);
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const currentProcess = globalThis.process;
|
|
8
|
+
const envPath = path.resolve(currentProcess.cwd(), '.env');
|
|
9
|
+
|
|
10
|
+
if (fs.existsSync(envPath)) {
|
|
11
|
+
currentProcess.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const projectSlug = path
|
|
15
|
+
.basename(currentProcess.cwd())
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
18
|
+
.replace(/^-+|-+$/g, '')
|
|
19
|
+
.slice(0, 12);
|
|
20
|
+
const username = `${projectSlug || 'local'}-${crypto.randomBytes(3).toString('hex')}`;
|
|
21
|
+
const password = `${crypto.randomBytes(9).toString('base64url')}A1!`;
|
|
22
|
+
|
|
23
|
+
const envContents = [
|
|
24
|
+
'TEST_ENV=dev',
|
|
25
|
+
'TEST_RUN_ID=local',
|
|
26
|
+
'DEV_UI_BASE_URL=http://127.0.0.1:3000',
|
|
27
|
+
'DEV_API_BASE_URL=http://127.0.0.1:3001',
|
|
28
|
+
`DEV_APP_USERNAME=${username}`,
|
|
29
|
+
`DEV_APP_PASSWORD=${password}`,
|
|
30
|
+
`UI_DEMO_USERNAME=${username}`,
|
|
31
|
+
`UI_DEMO_PASSWORD=${password}`
|
|
32
|
+
].join('\n');
|
|
33
|
+
|
|
34
|
+
fs.writeFileSync(envPath, `${envContents}\n`, 'utf8');
|
|
35
|
+
currentProcess.stdout.write(
|
|
36
|
+
`Generated local .env with demo credentials for ${username}\n`
|
|
37
|
+
);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Builds a local Allure report from raw test results after a test run completes.
|
|
2
|
-
import { AllureReport, readConfig } from
|
|
3
|
-
import { readdir, rm, stat } from
|
|
4
|
-
import { join, resolve } from
|
|
5
|
-
import process from
|
|
2
|
+
import { AllureReport, readConfig } from '@allurereport/core';
|
|
3
|
+
import { readdir, rm, stat } from 'node:fs/promises';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
6
|
|
|
7
7
|
const cwd = process.cwd();
|
|
8
|
-
const resultsDir = resolve(cwd,
|
|
9
|
-
const outputDir = resolve(cwd,
|
|
8
|
+
const resultsDir = resolve(cwd, 'allure-results');
|
|
9
|
+
const outputDir = resolve(cwd, 'reports/allure');
|
|
10
10
|
|
|
11
11
|
const collectResultFiles = async () => {
|
|
12
12
|
const entries = (await readdir(resultsDir)).sort();
|
|
@@ -28,20 +28,24 @@ const generateReport = async () => {
|
|
|
28
28
|
try {
|
|
29
29
|
await stat(resultsDir);
|
|
30
30
|
} catch {
|
|
31
|
-
process.stdout.write(
|
|
31
|
+
process.stdout.write(
|
|
32
|
+
'Skipping Allure report generation because allure-results does not exist.\n'
|
|
33
|
+
);
|
|
32
34
|
return;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
const files = await collectResultFiles();
|
|
36
38
|
|
|
37
39
|
if (files.length === 0) {
|
|
38
|
-
process.stdout.write(
|
|
40
|
+
process.stdout.write(
|
|
41
|
+
'Skipping Allure report generation because no result files were found.\n'
|
|
42
|
+
);
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
await rm(outputDir, { force: true, recursive: true });
|
|
43
47
|
|
|
44
|
-
const config = await readConfig(cwd,
|
|
48
|
+
const config = await readConfig(cwd, 'allurerc.mjs', { output: outputDir });
|
|
45
49
|
const report = new AllureReport(config);
|
|
46
50
|
|
|
47
51
|
await report.start();
|
|
@@ -52,7 +56,9 @@ const generateReport = async () => {
|
|
|
52
56
|
|
|
53
57
|
await report.done();
|
|
54
58
|
|
|
55
|
-
process.stdout.write(
|
|
59
|
+
process.stdout.write(
|
|
60
|
+
'Allure report generated at reports/allure/index.html\n'
|
|
61
|
+
);
|
|
56
62
|
};
|
|
57
63
|
|
|
58
64
|
generateReport().catch((error) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Starter API journey that pairs with the deterministic demo API server.
|
|
2
|
-
import { expect, test } from
|
|
2
|
+
import { expect, test } from '../fixtures/test-fixtures';
|
|
3
3
|
|
|
4
|
-
test.describe(
|
|
5
|
-
test(
|
|
4
|
+
test.describe('API starter flow', () => {
|
|
5
|
+
test('create and list one person @regression', async ({
|
|
6
6
|
appConfig,
|
|
7
7
|
dataFactory,
|
|
8
8
|
request,
|
|
@@ -10,7 +10,7 @@ test.describe("API starter flow", () => {
|
|
|
10
10
|
}) => {
|
|
11
11
|
const person = dataFactory.person();
|
|
12
12
|
|
|
13
|
-
await stepLogger.run(
|
|
13
|
+
await stepLogger.run('Create one person through the API', async () => {
|
|
14
14
|
const response = await request.post(`${appConfig.apiBaseUrl}/people`, {
|
|
15
15
|
data: person
|
|
16
16
|
});
|
|
@@ -18,12 +18,14 @@ test.describe("API starter flow", () => {
|
|
|
18
18
|
expect(await response.json()).toMatchObject(person);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
await stepLogger.run(
|
|
21
|
+
await stepLogger.run('List people and verify the new record', async () => {
|
|
22
22
|
const response = await request.get(`${appConfig.apiBaseUrl}/people`);
|
|
23
23
|
expect(response.ok()).toBeTruthy();
|
|
24
24
|
|
|
25
25
|
const people = await response.json();
|
|
26
|
-
expect(people).toContainEqual(
|
|
26
|
+
expect(people).toContainEqual(
|
|
27
|
+
expect.objectContaining({ personId: person.personId })
|
|
28
|
+
);
|
|
27
29
|
});
|
|
28
30
|
});
|
|
29
31
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Starter UI journey that shows the preferred Playwright test style in this template.
|
|
2
|
-
import { expect, test } from
|
|
2
|
+
import { expect, test } from '../fixtures/test-fixtures';
|
|
3
3
|
|
|
4
|
-
test.describe(
|
|
5
|
-
test(
|
|
4
|
+
test.describe('UI starter journey', () => {
|
|
5
|
+
test('login and add one person @smoke @critical', async ({
|
|
6
6
|
appConfig,
|
|
7
7
|
loginPage,
|
|
8
8
|
peoplePage,
|
|
@@ -11,16 +11,21 @@ test.describe("UI starter journey", () => {
|
|
|
11
11
|
}) => {
|
|
12
12
|
const person = dataFactory.person();
|
|
13
13
|
|
|
14
|
-
await stepLogger.run(
|
|
14
|
+
await stepLogger.run('Sign in to the demo app', async () => {
|
|
15
15
|
await loginPage.goto();
|
|
16
|
-
await loginPage.login(
|
|
16
|
+
await loginPage.login(
|
|
17
|
+
appConfig.credentials.username,
|
|
18
|
+
appConfig.credentials.password
|
|
19
|
+
);
|
|
17
20
|
await peoplePage.waitForReady();
|
|
18
|
-
expect(await peoplePage.getWelcomeMessage()).toContain(
|
|
21
|
+
expect(await peoplePage.getWelcomeMessage()).toContain(
|
|
22
|
+
appConfig.credentials.username
|
|
23
|
+
);
|
|
19
24
|
});
|
|
20
25
|
|
|
21
|
-
await stepLogger.run(
|
|
26
|
+
await stepLogger.run('Add one person and verify the list', async () => {
|
|
22
27
|
await peoplePage.addPerson(person);
|
|
23
|
-
expect(await peoplePage.flashMessage.getText()).toContain(
|
|
28
|
+
expect(await peoplePage.flashMessage.getText()).toContain('Person added');
|
|
24
29
|
expect(await peoplePage.getPersonSummary(person.personId)).toEqual({
|
|
25
30
|
name: person.name,
|
|
26
31
|
role: person.role,
|
|
@@ -3,23 +3,15 @@
|
|
|
3
3
|
"target": "ES2022",
|
|
4
4
|
"module": "commonjs",
|
|
5
5
|
"moduleResolution": "node",
|
|
6
|
-
"lib": [
|
|
7
|
-
|
|
8
|
-
"DOM"
|
|
9
|
-
],
|
|
10
|
-
"types": [
|
|
11
|
-
"node",
|
|
12
|
-
"@playwright/test"
|
|
13
|
-
],
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"types": ["node", "@playwright/test"],
|
|
14
8
|
"strict": true,
|
|
15
9
|
"esModuleInterop": true,
|
|
16
10
|
"resolveJsonModule": true,
|
|
17
11
|
"skipLibCheck": true,
|
|
18
12
|
"baseUrl": "."
|
|
19
13
|
},
|
|
20
|
-
"include": [
|
|
21
|
-
"**/*.ts"
|
|
22
|
-
],
|
|
14
|
+
"include": ["**/*.ts"],
|
|
23
15
|
"exclude": [
|
|
24
16
|
"node_modules",
|
|
25
17
|
"reports",
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
// Structured JSON logger used by tests, pages, and helper classes.
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
type LogLevel =
|
|
5
|
+
type LogLevel = 'info' | 'error';
|
|
6
6
|
|
|
7
7
|
type LogContext = Record<string, string | number | boolean | null | undefined>;
|
|
8
8
|
|
|
9
|
-
const LOG_FILE = path.resolve(process.cwd(),
|
|
9
|
+
const LOG_FILE = path.resolve(process.cwd(), 'reports/logs/execution.log');
|
|
10
10
|
|
|
11
11
|
function ensureLogDirectory(): void {
|
|
12
12
|
fs.mkdirSync(path.dirname(LOG_FILE), { recursive: true });
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function serialize(
|
|
15
|
+
function serialize(
|
|
16
|
+
level: LogLevel,
|
|
17
|
+
message: string,
|
|
18
|
+
context?: LogContext
|
|
19
|
+
): string {
|
|
16
20
|
return JSON.stringify({
|
|
17
21
|
timestamp: new Date().toISOString(),
|
|
18
22
|
level,
|
|
@@ -34,11 +38,11 @@ export class Logger {
|
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
info(message: string, context?: LogContext): void {
|
|
37
|
-
this.write(
|
|
41
|
+
this.write('info', message, context);
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
error(message: string, context?: LogContext): void {
|
|
41
|
-
this.write(
|
|
45
|
+
this.write('error', message, context);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
private write(level: LogLevel, message: string, context?: LogContext): void {
|
|
@@ -46,7 +50,7 @@ export class Logger {
|
|
|
46
50
|
...this.context,
|
|
47
51
|
...context
|
|
48
52
|
});
|
|
49
|
-
fs.appendFileSync(LOG_FILE, `${line}\n`,
|
|
53
|
+
fs.appendFileSync(LOG_FILE, `${line}\n`, 'utf8');
|
|
50
54
|
console.log(line);
|
|
51
55
|
}
|
|
52
56
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// Small wrapper that records named test steps in a consistent way.
|
|
2
|
-
import { test } from
|
|
2
|
+
import { test } from '@playwright/test';
|
|
3
3
|
|
|
4
|
-
import type { Logger } from
|
|
4
|
+
import type { Logger } from './logger';
|
|
5
5
|
|
|
6
6
|
export class StepLogger {
|
|
7
7
|
constructor(private readonly logger: Logger) {}
|
|
8
8
|
|
|
9
9
|
async run<T>(message: string, callback: () => Promise<T>): Promise<T> {
|
|
10
|
-
this.logger.info(
|
|
10
|
+
this.logger.info('step.started', { step: message });
|
|
11
11
|
try {
|
|
12
12
|
const result = await test.step(message, callback);
|
|
13
|
-
this.logger.info(
|
|
13
|
+
this.logger.info('step.passed', { step: message });
|
|
14
14
|
return result;
|
|
15
15
|
} catch (error) {
|
|
16
|
-
this.logger.error(
|
|
16
|
+
this.logger.error('step.failed', {
|
|
17
17
|
step: message,
|
|
18
18
|
error: error instanceof Error ? error.message : String(error)
|
|
19
19
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
TEST_ENV=dev
|
|
2
|
+
TEST_RUN_ID=local
|
|
3
|
+
|
|
4
|
+
DEV_UI_BASE_URL=http://127.0.0.1:3000
|
|
5
|
+
DEV_APP_USERNAME=generate-a-local-username
|
|
6
|
+
DEV_APP_PASSWORD=generate-a-local-password
|
|
7
|
+
|
|
8
|
+
STAGING_UI_BASE_URL=https://staging-ui.example.internal
|
|
9
|
+
STAGING_APP_USERNAME=your-staging-username
|
|
10
|
+
STAGING_APP_PASSWORD=your-staging-password
|
|
11
|
+
|
|
12
|
+
PROD_UI_BASE_URL=https://ui.example.internal
|
|
13
|
+
PROD_APP_USERNAME=your-prod-username
|
|
14
|
+
PROD_APP_PASSWORD=your-prod-password
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: WebdriverIO Template Validation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
paths:
|
|
7
|
+
- 'templates/wdio-template/**'
|
|
8
|
+
- 'test-apps/**'
|
|
9
|
+
- 'package.json'
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
wdio:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: '20'
|
|
21
|
+
|
|
22
|
+
- name: Install template dependencies
|
|
23
|
+
working-directory: templates/wdio-template
|
|
24
|
+
run: npm ci
|
|
25
|
+
|
|
26
|
+
- name: Run WebdriverIO validation
|
|
27
|
+
working-directory: templates/wdio-template
|
|
28
|
+
env:
|
|
29
|
+
TEST_ENV: dev
|
|
30
|
+
TEST_RUN_ID: ci
|
|
31
|
+
run: bash ./scripts/run-tests.sh
|
|
32
|
+
|
|
33
|
+
- name: Generate Allure report
|
|
34
|
+
if: always()
|
|
35
|
+
working-directory: templates/wdio-template
|
|
36
|
+
run: npm run report:allure
|
|
37
|
+
|
|
38
|
+
- name: Upload WebdriverIO artifacts
|
|
39
|
+
if: always()
|
|
40
|
+
uses: actions/upload-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: wdio-template-artifacts
|
|
43
|
+
if-no-files-found: ignore
|
|
44
|
+
path: |
|
|
45
|
+
templates/wdio-template/reports
|
|
46
|
+
templates/wdio-template/allure-results
|