@toolstackhq/create-qa-patterns 1.0.0 → 1.0.1
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 +17 -10
- package/index.js +197 -1
- package/package.json +4 -3
- package/templates/playwright-template/.env.example +17 -0
- package/templates/playwright-template/.github/workflows/playwright-tests.yml +126 -0
- package/templates/playwright-template/README.md +234 -0
- package/templates/playwright-template/allurerc.mjs +10 -0
- package/templates/playwright-template/components/flash-message.ts +16 -0
- package/templates/playwright-template/config/environments.ts +41 -0
- package/templates/playwright-template/config/runtime-config.ts +53 -0
- package/templates/playwright-template/config/secret-manager.ts +28 -0
- package/templates/playwright-template/config/test-env.ts +9 -0
- package/templates/playwright-template/data/README.md +9 -0
- package/templates/playwright-template/data/factories/data-factory.ts +33 -0
- package/templates/playwright-template/data/generators/id-generator.ts +17 -0
- package/templates/playwright-template/data/generators/seeded-faker.ts +13 -0
- package/templates/playwright-template/docker/Dockerfile +21 -0
- package/templates/playwright-template/eslint.config.mjs +66 -0
- package/templates/playwright-template/fixtures/test-fixtures.ts +43 -0
- package/templates/playwright-template/lint/architecture-plugin.cjs +118 -0
- package/templates/playwright-template/package-lock.json +4724 -0
- package/templates/playwright-template/package.json +34 -0
- package/templates/playwright-template/pages/base-page.ts +24 -0
- package/templates/playwright-template/pages/login-page.ts +22 -0
- package/templates/playwright-template/pages/people-page.ts +39 -0
- package/templates/playwright-template/playwright.config.ts +46 -0
- package/templates/playwright-template/reporters/structured-reporter.ts +61 -0
- package/templates/playwright-template/scripts/generate-allure-report.mjs +57 -0
- package/templates/playwright-template/scripts/run-tests.sh +6 -0
- package/templates/playwright-template/tests/api-people.spec.ts +28 -0
- package/templates/playwright-template/tests/ui-journey.spec.ts +30 -0
- package/templates/playwright-template/tsconfig.json +31 -0
- package/templates/playwright-template/utils/logger.ts +55 -0
- package/templates/playwright-template/utils/test-step.ts +22 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { TestEnvironment } from "./test-env";
|
|
2
|
+
|
|
3
|
+
type EnvironmentDefaults = {
|
|
4
|
+
uiBaseUrl: string;
|
|
5
|
+
apiBaseUrl: string;
|
|
6
|
+
credentials: {
|
|
7
|
+
username: string;
|
|
8
|
+
password: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const DEFAULTS: Record<TestEnvironment, EnvironmentDefaults> = {
|
|
13
|
+
dev: {
|
|
14
|
+
uiBaseUrl: "http://127.0.0.1:3000",
|
|
15
|
+
apiBaseUrl: "http://127.0.0.1:3001",
|
|
16
|
+
credentials: {
|
|
17
|
+
username: "tester",
|
|
18
|
+
password: "Password123!"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
staging: {
|
|
22
|
+
uiBaseUrl: "https://staging-ui.example.internal",
|
|
23
|
+
apiBaseUrl: "https://staging-api.example.internal",
|
|
24
|
+
credentials: {
|
|
25
|
+
username: "staging-user",
|
|
26
|
+
password: "replace-me"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
prod: {
|
|
30
|
+
uiBaseUrl: "https://ui.example.internal",
|
|
31
|
+
apiBaseUrl: "https://api.example.internal",
|
|
32
|
+
credentials: {
|
|
33
|
+
username: "prod-user",
|
|
34
|
+
password: "replace-me"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function getEnvironmentDefaults(testEnv: TestEnvironment): EnvironmentDefaults {
|
|
40
|
+
return DEFAULTS[testEnv];
|
|
41
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import dotenv from "dotenv";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { getEnvironmentDefaults } from "./environments";
|
|
7
|
+
import { EnvSecretProvider, SecretManager } from "./secret-manager";
|
|
8
|
+
import { loadTestEnvironment } from "./test-env";
|
|
9
|
+
|
|
10
|
+
const environment = loadTestEnvironment();
|
|
11
|
+
|
|
12
|
+
dotenv.config({ path: path.resolve(process.cwd(), ".env") });
|
|
13
|
+
dotenv.config({ path: path.resolve(process.cwd(), `.env.${environment}`), override: true });
|
|
14
|
+
|
|
15
|
+
const runtimeConfigSchema = z.object({
|
|
16
|
+
testEnv: z.enum(["dev", "staging", "prod"]),
|
|
17
|
+
testRunId: z.string().min(1),
|
|
18
|
+
uiBaseUrl: z.string().url(),
|
|
19
|
+
apiBaseUrl: z.string().url(),
|
|
20
|
+
credentials: z.object({
|
|
21
|
+
username: z.string().min(1),
|
|
22
|
+
password: z.string().min(1)
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export type RuntimeConfig = z.infer<typeof runtimeConfigSchema>;
|
|
27
|
+
|
|
28
|
+
export function loadRuntimeConfig(): RuntimeConfig {
|
|
29
|
+
const defaults = getEnvironmentDefaults(environment);
|
|
30
|
+
const secretManager = new SecretManager(new EnvSecretProvider());
|
|
31
|
+
|
|
32
|
+
const uiBaseUrl =
|
|
33
|
+
process.env[`${environment.toUpperCase()}_UI_BASE_URL`] ??
|
|
34
|
+
process.env.UI_BASE_URL ??
|
|
35
|
+
defaults.uiBaseUrl;
|
|
36
|
+
const apiBaseUrl =
|
|
37
|
+
process.env[`${environment.toUpperCase()}_API_BASE_URL`] ??
|
|
38
|
+
process.env.API_BASE_URL ??
|
|
39
|
+
defaults.apiBaseUrl;
|
|
40
|
+
|
|
41
|
+
return runtimeConfigSchema.parse({
|
|
42
|
+
testEnv: environment,
|
|
43
|
+
testRunId: process.env.TEST_RUN_ID ?? "local",
|
|
44
|
+
uiBaseUrl,
|
|
45
|
+
apiBaseUrl,
|
|
46
|
+
credentials: {
|
|
47
|
+
username:
|
|
48
|
+
secretManager.getOptionalSecret("APP_USERNAME", environment) ?? defaults.credentials.username,
|
|
49
|
+
password:
|
|
50
|
+
secretManager.getOptionalSecret("APP_PASSWORD", environment) ?? defaults.credentials.password
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TestEnvironment } from "./test-env";
|
|
2
|
+
|
|
3
|
+
export interface SecretProvider {
|
|
4
|
+
getSecret(key: string, testEnv: TestEnvironment): string | undefined;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class EnvSecretProvider implements SecretProvider {
|
|
8
|
+
getSecret(key: string, testEnv: TestEnvironment): string | undefined {
|
|
9
|
+
const envPrefix = testEnv.toUpperCase();
|
|
10
|
+
return process.env[`${envPrefix}_${key}`] ?? process.env[key];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class SecretManager {
|
|
15
|
+
constructor(private readonly provider: SecretProvider) {}
|
|
16
|
+
|
|
17
|
+
getRequiredSecret(key: string, testEnv: TestEnvironment): string {
|
|
18
|
+
const value = this.provider.getSecret(key, testEnv);
|
|
19
|
+
if (!value) {
|
|
20
|
+
throw new Error(`Missing secret "${key}" for TEST_ENV=${testEnv}`);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getOptionalSecret(key: string, testEnv: TestEnvironment): string | undefined {
|
|
26
|
+
return this.provider.getSecret(key, testEnv);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const testEnvironmentSchema = z.enum(["dev", "staging", "prod"]);
|
|
4
|
+
|
|
5
|
+
export type TestEnvironment = z.infer<typeof testEnvironmentSchema>;
|
|
6
|
+
|
|
7
|
+
export function loadTestEnvironment(): TestEnvironment {
|
|
8
|
+
return testEnvironmentSchema.parse(process.env.TEST_ENV ?? "dev");
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Data Layer
|
|
2
|
+
|
|
3
|
+
Keep the starter data layer generic.
|
|
4
|
+
|
|
5
|
+
- Start with `DataFactory.person()` or similarly small builders.
|
|
6
|
+
- Add domain-specific factories only when the project actually needs them.
|
|
7
|
+
- Introduce stricter validation later only if your team actually needs it.
|
|
8
|
+
|
|
9
|
+
The template should feel approachable before it feels comprehensive.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createSeededFaker } from "../generators/seeded-faker";
|
|
2
|
+
import { IdGenerator } from "../generators/id-generator";
|
|
3
|
+
|
|
4
|
+
export type PersonRecord = {
|
|
5
|
+
personId: string;
|
|
6
|
+
name: string;
|
|
7
|
+
role: string;
|
|
8
|
+
email: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class DataFactory {
|
|
12
|
+
private readonly idGenerator: IdGenerator;
|
|
13
|
+
|
|
14
|
+
constructor(private readonly testRunId: string) {
|
|
15
|
+
this.idGenerator = new IdGenerator(testRunId);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
person(overrides?: Partial<PersonRecord>): PersonRecord {
|
|
19
|
+
const personId = overrides?.personId ?? this.idGenerator.next("person");
|
|
20
|
+
const seededFaker = createSeededFaker(`${this.testRunId}:${personId}`);
|
|
21
|
+
const firstName = seededFaker.person.firstName();
|
|
22
|
+
const lastName = seededFaker.person.lastName();
|
|
23
|
+
const name = `${firstName} ${lastName}`;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
personId,
|
|
27
|
+
name,
|
|
28
|
+
role: overrides?.role ?? seededFaker.person.jobTitle(),
|
|
29
|
+
email: overrides?.email ?? `${firstName}.${lastName}.${personId}@example.test`.toLowerCase(),
|
|
30
|
+
...overrides
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class IdGenerator {
|
|
2
|
+
private readonly counters = new Map<string, number>();
|
|
3
|
+
|
|
4
|
+
constructor(private readonly runId: string) {}
|
|
5
|
+
|
|
6
|
+
next(prefix: string): string {
|
|
7
|
+
const counter = (this.counters.get(prefix) ?? 0) + 1;
|
|
8
|
+
this.counters.set(prefix, counter);
|
|
9
|
+
return `${prefix}-${this.runId}-${String(counter).padStart(4, "0")}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
nextSequence(prefix: string): number {
|
|
13
|
+
const counter = (this.counters.get(prefix) ?? 0) + 1;
|
|
14
|
+
this.counters.set(prefix, counter);
|
|
15
|
+
return counter;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { faker } from "@faker-js/faker";
|
|
2
|
+
|
|
3
|
+
function hashSeed(value: string): number {
|
|
4
|
+
return value.split("").reduce((seed, character) => {
|
|
5
|
+
return ((seed << 5) - seed + character.charCodeAt(0)) | 0;
|
|
6
|
+
}, 0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createSeededFaker(seedInput: string) {
|
|
10
|
+
const instance = faker;
|
|
11
|
+
instance.seed(Math.abs(hashSeed(seedInput)));
|
|
12
|
+
return instance;
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
FROM node:20-bookworm-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /workspace
|
|
4
|
+
|
|
5
|
+
COPY package.json package-lock.json tsconfig.json playwright.config.ts eslint.config.mjs allurerc.mjs ./
|
|
6
|
+
COPY config ./config
|
|
7
|
+
COPY components ./components
|
|
8
|
+
COPY data ./data
|
|
9
|
+
COPY fixtures ./fixtures
|
|
10
|
+
COPY lint ./lint
|
|
11
|
+
COPY pages ./pages
|
|
12
|
+
COPY reporters ./reporters
|
|
13
|
+
COPY scripts ./scripts
|
|
14
|
+
COPY tests ./tests
|
|
15
|
+
COPY utils ./utils
|
|
16
|
+
COPY .env.example ./.env.example
|
|
17
|
+
|
|
18
|
+
RUN npm ci
|
|
19
|
+
RUN npx playwright install --with-deps chromium
|
|
20
|
+
|
|
21
|
+
CMD ["bash", "./scripts/run-tests.sh"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import tseslint from "@typescript-eslint/eslint-plugin";
|
|
3
|
+
import tsParser from "@typescript-eslint/parser";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import architecturePlugin from "./lint/architecture-plugin.cjs";
|
|
7
|
+
|
|
8
|
+
const configDirectory = fileURLToPath(new globalThis.URL(".", import.meta.url));
|
|
9
|
+
|
|
10
|
+
export default [
|
|
11
|
+
{
|
|
12
|
+
ignores: [
|
|
13
|
+
"node_modules/**",
|
|
14
|
+
"reports/**",
|
|
15
|
+
"allure-results/**",
|
|
16
|
+
"allure-report/**",
|
|
17
|
+
"test-results/**",
|
|
18
|
+
"playwright-report/**"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
js.configs.recommended,
|
|
22
|
+
{
|
|
23
|
+
files: ["**/*.ts"],
|
|
24
|
+
languageOptions: {
|
|
25
|
+
parser: tsParser,
|
|
26
|
+
parserOptions: {
|
|
27
|
+
project: "./tsconfig.json",
|
|
28
|
+
tsconfigRootDir: configDirectory
|
|
29
|
+
},
|
|
30
|
+
globals: {
|
|
31
|
+
console: "readonly",
|
|
32
|
+
process: "readonly",
|
|
33
|
+
URL: "readonly"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
plugins: {
|
|
37
|
+
"@typescript-eslint": tseslint,
|
|
38
|
+
architecture: architecturePlugin
|
|
39
|
+
},
|
|
40
|
+
rules: {
|
|
41
|
+
...tseslint.configs.recommended.rules,
|
|
42
|
+
"@typescript-eslint/naming-convention": [
|
|
43
|
+
"error",
|
|
44
|
+
{
|
|
45
|
+
selector: "default",
|
|
46
|
+
format: ["camelCase"],
|
|
47
|
+
leadingUnderscore: "allow"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
selector: "typeLike",
|
|
51
|
+
format: ["PascalCase"]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
selector: "variable",
|
|
55
|
+
modifiers: ["const"],
|
|
56
|
+
format: ["camelCase", "UPPER_CASE", "PascalCase"]
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
60
|
+
"no-empty-pattern": "off",
|
|
61
|
+
"architecture/no-raw-locators-in-tests": "error",
|
|
62
|
+
"architecture/no-wait-for-timeout": "error",
|
|
63
|
+
"architecture/no-expect-in-page-objects": "error"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { test as base } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
import { loadRuntimeConfig, type RuntimeConfig } from "../config/runtime-config";
|
|
4
|
+
import { DataFactory } from "../data/factories/data-factory";
|
|
5
|
+
import { LoginPage } from "../pages/login-page";
|
|
6
|
+
import { PeoplePage } from "../pages/people-page";
|
|
7
|
+
import { createLogger, type Logger } from "../utils/logger";
|
|
8
|
+
import { StepLogger } from "../utils/test-step";
|
|
9
|
+
|
|
10
|
+
type FrameworkFixtures = {
|
|
11
|
+
appConfig: RuntimeConfig;
|
|
12
|
+
logger: Logger;
|
|
13
|
+
stepLogger: StepLogger;
|
|
14
|
+
dataFactory: DataFactory;
|
|
15
|
+
loginPage: LoginPage;
|
|
16
|
+
peoplePage: PeoplePage;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const test = base.extend<FrameworkFixtures>({
|
|
20
|
+
appConfig: async ({}, use) => {
|
|
21
|
+
await use(loadRuntimeConfig());
|
|
22
|
+
},
|
|
23
|
+
logger: async ({}, use, testInfo) => {
|
|
24
|
+
const logger = createLogger({
|
|
25
|
+
test: testInfo.titlePath.join(" > ")
|
|
26
|
+
});
|
|
27
|
+
await use(logger);
|
|
28
|
+
},
|
|
29
|
+
stepLogger: async ({ logger }, use) => {
|
|
30
|
+
await use(new StepLogger(logger.child({ scope: "steps" })));
|
|
31
|
+
},
|
|
32
|
+
dataFactory: async ({ appConfig }, use) => {
|
|
33
|
+
await use(new DataFactory(appConfig.testRunId));
|
|
34
|
+
},
|
|
35
|
+
loginPage: async ({ page, appConfig, logger }, use) => {
|
|
36
|
+
await use(new LoginPage(page, appConfig.uiBaseUrl, logger.child({ pageObject: "LoginPage" })));
|
|
37
|
+
},
|
|
38
|
+
peoplePage: async ({ page, appConfig, logger }, use) => {
|
|
39
|
+
await use(new PeoplePage(page, appConfig.uiBaseUrl, logger.child({ pageObject: "PeoplePage" })));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export { expect } from "@playwright/test";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const TEST_FILE_PATTERN = /[/\\]tests[/\\].+\.ts$/;
|
|
2
|
+
const PAGE_OBJECT_PATTERN = /[/\\](pages|components)[/\\].+\.ts$/;
|
|
3
|
+
|
|
4
|
+
const locatorMethods = new Set([
|
|
5
|
+
"locator",
|
|
6
|
+
"getByRole",
|
|
7
|
+
"getByLabel",
|
|
8
|
+
"getByTestId",
|
|
9
|
+
"getByText",
|
|
10
|
+
"getByPlaceholder",
|
|
11
|
+
"getByAltText",
|
|
12
|
+
"getByTitle",
|
|
13
|
+
"$",
|
|
14
|
+
"$$"
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function isIdentifierProperty(node, name) {
|
|
18
|
+
return (
|
|
19
|
+
node &&
|
|
20
|
+
node.type === "MemberExpression" &&
|
|
21
|
+
!node.computed &&
|
|
22
|
+
node.property &&
|
|
23
|
+
node.property.type === "Identifier" &&
|
|
24
|
+
node.property.name === name
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
rules: {
|
|
30
|
+
"no-raw-locators-in-tests": {
|
|
31
|
+
meta: {
|
|
32
|
+
type: "problem",
|
|
33
|
+
docs: {
|
|
34
|
+
description: "Disallow selectors inside test files"
|
|
35
|
+
},
|
|
36
|
+
schema: [],
|
|
37
|
+
messages: {
|
|
38
|
+
noRawLocators:
|
|
39
|
+
"Raw locators are not allowed in tests. Move selector logic into a page object or component."
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
create(context) {
|
|
43
|
+
if (!TEST_FILE_PATTERN.test(context.getFilename())) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
CallExpression(node) {
|
|
49
|
+
if (
|
|
50
|
+
node.callee.type === "MemberExpression" &&
|
|
51
|
+
!node.callee.computed &&
|
|
52
|
+
node.callee.property.type === "Identifier" &&
|
|
53
|
+
locatorMethods.has(node.callee.property.name)
|
|
54
|
+
) {
|
|
55
|
+
context.report({
|
|
56
|
+
node,
|
|
57
|
+
messageId: "noRawLocators"
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"no-wait-for-timeout": {
|
|
65
|
+
meta: {
|
|
66
|
+
type: "problem",
|
|
67
|
+
docs: {
|
|
68
|
+
description: "Disallow waitForTimeout"
|
|
69
|
+
},
|
|
70
|
+
schema: [],
|
|
71
|
+
messages: {
|
|
72
|
+
noWaitForTimeout: "waitForTimeout is not allowed. Synchronize with user-visible events instead."
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
create(context) {
|
|
76
|
+
return {
|
|
77
|
+
CallExpression(node) {
|
|
78
|
+
if (isIdentifierProperty(node.callee, "waitForTimeout")) {
|
|
79
|
+
context.report({
|
|
80
|
+
node,
|
|
81
|
+
messageId: "noWaitForTimeout"
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"no-expect-in-page-objects": {
|
|
89
|
+
meta: {
|
|
90
|
+
type: "problem",
|
|
91
|
+
docs: {
|
|
92
|
+
description: "Disallow assertions inside page objects and UI components"
|
|
93
|
+
},
|
|
94
|
+
schema: [],
|
|
95
|
+
messages: {
|
|
96
|
+
noExpect:
|
|
97
|
+
"Assertions are not allowed inside page objects or components. Return state and assert from the test."
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
create(context) {
|
|
101
|
+
if (!PAGE_OBJECT_PATTERN.test(context.getFilename())) {
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
CallExpression(node) {
|
|
107
|
+
if (node.callee.type === "Identifier" && node.callee.name === "expect") {
|
|
108
|
+
context.report({
|
|
109
|
+
node,
|
|
110
|
+
messageId: "noExpect"
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|