create-pw-core 1.2.0 → 1.2.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/dist/index.js CHANGED
@@ -48,7 +48,11 @@ const question = (query) => {
48
48
  async function runCommand(command, args, cwd) {
49
49
  return new Promise((resolve, reject) => {
50
50
  console.log(`\x1b[36mRunning: ${command} ${args.join(' ')}\x1b[0m`);
51
- const child = (0, child_process_1.spawn)(command, args, { cwd, stdio: ['ignore', 'inherit', 'inherit'], shell: true });
51
+ const child = (0, child_process_1.spawn)(command, args, {
52
+ cwd,
53
+ stdio: ['ignore', 'inherit', 'inherit'],
54
+ shell: true
55
+ });
52
56
  child.on('close', (code) => {
53
57
  if (code === 0) {
54
58
  resolve();
@@ -106,9 +110,7 @@ async function main() {
106
110
  console.log('\x1b[35m Initializing pw-core Test Suite \x1b[0m');
107
111
  console.log('\x1b[35m============================================\n\x1b[0m');
108
112
  const projectPathInput = await question('Project path (default: current directory): ');
109
- const targetDir = projectPathInput.trim()
110
- ? path.resolve(process.cwd(), projectPathInput.trim())
111
- : process.cwd();
113
+ const targetDir = projectPathInput.trim() ? path.resolve(process.cwd(), projectPathInput.trim()) : process.cwd();
112
114
  if (!fs.existsSync(targetDir)) {
113
115
  fs.mkdirSync(targetDir, { recursive: true });
114
116
  }
@@ -134,7 +136,9 @@ async function main() {
134
136
  const latestVersion = (0, child_process_1.execSync)('npm view create-pw-core version', {
135
137
  stdio: ['ignore', 'pipe', 'ignore'],
136
138
  timeout: 3000
137
- }).toString().trim();
139
+ })
140
+ .toString()
141
+ .trim();
138
142
  if (latestVersion && currentVersion !== latestVersion) {
139
143
  console.log(`\n\x1b[33mNewer version of create-pw-core found (${latestVersion}). Current: ${currentVersion}\x1b[0m`);
140
144
  console.log('\x1b[36mRunning create-pw-core@latest to ensure you have the latest features...\n\x1b[0m');
@@ -202,13 +206,13 @@ async function main() {
202
206
  const targetPkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
203
207
  // Merge description, author, license if target has empty or default ones
204
208
  if (!targetPkg.description || targetPkg.description === 'pw-core test suite') {
205
- targetPkg.description = templatePkg.description || "pw-core test suite";
209
+ targetPkg.description = templatePkg.description || 'pw-core test suite';
206
210
  }
207
211
  if (!targetPkg.author && templatePkg.author) {
208
212
  targetPkg.author = templatePkg.author;
209
213
  }
210
214
  if (!targetPkg.license || targetPkg.license === 'ISC') {
211
- targetPkg.license = templatePkg.license || "ISC";
215
+ targetPkg.license = templatePkg.license || 'ISC';
212
216
  }
213
217
  // Merge scripts from template
214
218
  targetPkg.scripts = targetPkg.scripts || {};
@@ -0,0 +1,8 @@
1
+ {
2
+ "semi": false,
3
+ "singleQuote": true,
4
+ "trailingComma": "none",
5
+ "printWidth": 120,
6
+ "tabWidth": 2,
7
+ "useTabs": false
8
+ }
@@ -5,5 +5,6 @@
5
5
  "test-results": true,
6
6
  "package-lock.json": true,
7
7
  "yarn.lock": true
8
- }
8
+ },
9
+ "editor.formatOnSave": true,
9
10
  }
@@ -45,3 +45,18 @@ You can review or customize these settings in:
45
45
  ```bash
46
46
  npm run test:report
47
47
  ```
48
+
49
+ - **Run pw-core codegen** (Interactive page object locator recorder):
50
+ ```bash
51
+ npm run codegen
52
+ ```
53
+ - **Run pw-core codegen in safe mode** (Prevent modifying existing registry configurations):
54
+ ```bash
55
+ npm run codegen:safe
56
+ ```
57
+
58
+ > [!TIP]
59
+ > **Windows Users**: If you get a security error saying `running scripts is disabled on this system`, run this command in your PowerShell terminal to enable it:
60
+ > ```powershell
61
+ > Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
62
+ > ```
@@ -0,0 +1,78 @@
1
+ import js from '@eslint/js'
2
+ import tseslint from 'typescript-eslint'
3
+ import stylistic from '@stylistic/eslint-plugin'
4
+
5
+ export default tseslint.config(
6
+ // ── Ignored paths ──────────────────────────────────────────────────────────
7
+ {
8
+ ignores: ['node_modules/**', 'playwright-report/**', 'test-results/**'],
9
+ },
10
+
11
+ // ── Base recommended rules ─────────────────────────────────────────────────
12
+ js.configs.recommended,
13
+ ...tseslint.configs.recommended,
14
+
15
+ // ── Core rules for all TS files ────────────────────────────────────────────
16
+ {
17
+ plugins: {
18
+ '@stylistic': stylistic,
19
+ },
20
+
21
+ rules: {
22
+ // --- Variable declarations ---
23
+ 'prefer-const': 'error', // Use const when variable is never reassigned
24
+ 'no-var': 'error', // Disallow var; use let or const
25
+
26
+ // --- Code quality ---
27
+ 'eqeqeq': ['error', 'always'], // Enforce === over ==
28
+ 'no-console': 'warn', // Warn on console.* calls
29
+ 'no-duplicate-imports': 'error', // No multiple imports from the same module
30
+
31
+ // --- TypeScript-specific ---
32
+ '@typescript-eslint/no-unused-vars': [
33
+ 'error',
34
+ {
35
+ argsIgnorePattern: '^_', // Allow unused args prefixed with _
36
+ varsIgnorePattern: '^_', // Allow unused vars prefixed with _
37
+ caughtErrorsIgnorePattern: '^_',
38
+ },
39
+ ],
40
+ '@typescript-eslint/no-explicit-any': 'warn', // Discourage any type
41
+ '@typescript-eslint/consistent-type-imports': [ // Enforce `import type` for type-only imports
42
+ 'error',
43
+ { prefer: 'type-imports', fixStyle: 'inline-type-imports' },
44
+ ],
45
+ '@typescript-eslint/no-require-imports': 'error', // Prefer ES imports over require()
46
+
47
+ // --- Blank lines & formatting ---
48
+ '@stylistic/no-multiple-empty-lines': [
49
+ 'error',
50
+ {
51
+ max: 1, // At most 1 blank line anywhere (keeps logical grouping in functions)
52
+ maxBOF: 0, // No blank lines at start of file
53
+ maxEOF: 1, // Allow 1 blank line at end of file
54
+ },
55
+ ],
56
+
57
+ // Allow blank lines before/after blocks and between import groups,
58
+ // but not between object properties
59
+ '@stylistic/padding-line-between-statements': [
60
+ 'error',
61
+ { blankLine: 'always', prev: 'import', next: '*' },
62
+ { blankLine: 'any', prev: 'import', next: 'import' }, // no blank between imports
63
+ { blankLine: 'always', prev: '*', next: 'return' }, // blank before return
64
+ { blankLine: 'always', prev: ['const', 'let'], next: '*' },
65
+ { blankLine: 'any', prev: ['const', 'let'], next: ['const', 'let'] }, // consecutive decls OK
66
+ ],
67
+ },
68
+ },
69
+
70
+ // ── Relaxed rules for test files ───────────────────────────────────────────
71
+ {
72
+ files: ['**/*.test.ts', '**/playwright.config.ts'],
73
+ rules: {
74
+ 'no-console': 'off', // console.log is fine in tests/config
75
+ '@typescript-eslint/no-explicit-any': 'off', // test helpers often use any
76
+ },
77
+ }
78
+ )
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "pw-core-demo",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Demo and example test suite showcasing the usage of pw-core in Playwright.",
5
5
  "scripts": {
6
6
  "test": "playwright test",
7
7
  "test:headed": "playwright test --headed",
8
8
  "test:report": "playwright show-report",
9
- "test:ui": "playwright test --ui"
9
+ "test:ui": "playwright test --ui",
10
+ "codegen": "pw-core",
11
+ "codegen:safe": "npx pw-core codegen --safe",
12
+ "lint": "eslint .",
13
+ "lint:fix": "eslint . --fix"
10
14
  },
11
15
  "author": {
12
16
  "name": "Shanmuka Chandra Teja Anem",
@@ -14,10 +18,14 @@
14
18
  },
15
19
  "license": "MIT",
16
20
  "devDependencies": {
21
+ "@eslint/js": "^10.0.1",
17
22
  "@playwright/test": "^1.61.0",
23
+ "@stylistic/eslint-plugin": "^5.10.0",
18
24
  "@types/node": "^20.11.0",
19
25
  "dotenv": "^16.4.5",
20
- "pw-core": "^1.2.0",
21
- "typescript": "^6.0.3"
26
+ "eslint": "^10.6.0",
27
+ "pw-core": "^1.2.1",
28
+ "typescript": "^6.0.3",
29
+ "typescript-eslint": "^8.62.1"
22
30
  }
23
31
  }
@@ -1,5 +1,5 @@
1
1
  import { defineConfig } from '@playwright/test';
2
- import { env } from '@utils/env';
2
+ import { env } from 'src/utils/env';
3
3
 
4
4
  export default defineConfig({
5
5
  testDir: './src/tests',
@@ -0,0 +1,15 @@
1
+ import { registry } from './registry';
2
+
3
+ export class PlaygroundPage extends registry.pages.playground {
4
+
5
+ async fillInputForm(text: string, password: string, number: string) {
6
+ await this.fill('playgroundText', text);
7
+ await this.fill('playgroundPassword', password);
8
+ await this.fill('playgroundNumber', number);
9
+ }
10
+
11
+ async fillOtp(code: string) {
12
+ await this.click('playgroundOtp');
13
+ await this.page.keyboard.type(code);
14
+ }
15
+ }
@@ -1,73 +1,59 @@
1
1
  import { createPageRegistry } from 'pw-core/page';
2
2
 
3
- // Initialize registry (mostly automatic, defining base configs as siblings)
4
3
  export const registry = createPageRegistry({
5
4
  loginPage: {
6
5
  url: '/login',
7
6
  testIds: {
8
- defaultUserLogin: 'login-default-user',
9
- email: 'email-input',
10
- password: 'password-input',
11
- submit: 'login-submit',
7
+ defaultUserLogin: 'login-default-user'
12
8
  }
13
9
  },
14
10
  dashboardPage: {
15
11
  url: '/app',
16
12
  selectors: {
17
- heading: 'h1:has-text("Dashboard")',
18
- },
13
+ heading: 'h1:has-text("Dashboard")'
14
+ }
19
15
  },
20
16
  projectsPage: {
21
17
  url: '/app/projects',
22
18
  testIds: {
23
- newProject: 'new-project-button',
24
- table: 'projects-table',
25
- "form{item}": {
19
+ 'form{item}': {
26
20
  item: ['title', 'description', 'save'],
27
- testId: "form-item"
28
- }
21
+ testId: 'form-item'
22
+ },
23
+ newProject: 'new-project-button',
24
+ table: 'projects-table'
29
25
  }
30
26
  },
31
- tasksPage: {
32
- url: '/app/tasks',
33
- testIds: {
34
- newTask: 'new-task-button',
35
- table: 'tasks-table',
36
- "form{item}": {
37
- item: ['title', 'description', 'save'],
38
- testId: "form-item"
39
- }
40
- },
41
- },
42
27
  sidebar: {
43
28
  testIds: {
44
- itemProjects: 'sidebar-projects',
45
- itemTasks: 'sidebar-tasks',
46
- },
47
- },
48
- topNav: {
49
- testIds: {
50
- workspaceDropdown: 'active-workspace-btn',
51
- logoutBtn: 'ws-option-logout',
52
- },
29
+ 'item{page}': {
30
+ page: ['projects'],
31
+ testId: 'sidebar-page'
32
+ }
33
+ }
53
34
  },
54
35
  playground: {
55
36
  url: '/playground',
56
37
  testIds: {
57
- // Dynamic testIds: Keys converted to camelCase (e.g. activeLineChart), values kebab-cased (e.g. "active-line-chart")
58
- "{status}{id}Chart": {
59
- id: ['line', 'bar'],
60
- status: ['active', 'inactive'],
61
- testId: "status-id-chart"
38
+ 'tabTrigger{item}': {
39
+ item: ['inputs', 'buttons', 'tables', 'charts', 'overlays', 'advanced'],
40
+ testId: 'tab-trigger-item'
62
41
  }
63
42
  },
64
43
  selectors: {
65
- card: ".skeu-card",
66
- // Dynamic selectors: Keys camelCased (e.g. safe, danger), values preserve original casing (e.g. "#Safe", "#Danger")
67
- "{status}": {
68
- status: ['Safe', 'Danger'],
69
- selector: "#status"
44
+ 'btnVariant{item}': {
45
+ item: ['default', 'secondary', 'outline', 'destructive'],
46
+ selector: '#btn-variant-{item}'
70
47
  },
71
- }
48
+ 'toggleAlign{item}': {
49
+ item: ['left', 'center', 'right'],
50
+ selector: '#toggle-align-{item}'
51
+ },
52
+ 'playground{item}': {
53
+ item: ['text', 'password', 'number', 'switch', 'textarea', 'otp'],
54
+ selector: '#playground-{item}'
55
+ }
56
+ },
57
+ checkbox: ['Accept terms']
72
58
  }
73
59
  });
@@ -0,0 +1,40 @@
1
+ import { registry as test } from '@pages/registry';
2
+ import { scenario } from 'src/utils/fixtures';
3
+
4
+ // Parallel — each test runs in its own browser instance
5
+ test(
6
+ 'Verify login and project creation flow on /app',
7
+ async ({ loginPage, dashboardPage, projectsPage, sidebar }) => {
8
+ await loginPage.goto();
9
+ await loginPage.verifyTitle('PW-Core Workspace — Build, Test & Document');
10
+ await loginPage.waitForLoadState('networkidle');
11
+
12
+ await loginPage.verify('defaultUserLogin').toBeEnabled();
13
+ await loginPage.click('defaultUserLogin');
14
+ await dashboardPage.verifyURL();
15
+ await dashboardPage.verify('heading');
16
+
17
+ await sidebar.click('itemProjects');
18
+ await projectsPage.verifyURL();
19
+
20
+ await projectsPage.click('newProject');
21
+ await projectsPage.fill('formTitle', 'Demo Project');
22
+ await projectsPage.fill('formDescription', 'Created via pw-core automation');
23
+ await projectsPage.verifyEnabled('formSave');
24
+ await projectsPage.click('formSave');
25
+
26
+ await projectsPage.verify('table', { hasText: 'Demo Project' });
27
+ }
28
+ );
29
+
30
+ // Parallel — uses overridden PlaygroundPage class with custom helpers
31
+ scenario(
32
+ 'Verify playground inputs using overridden page class helpers',
33
+ async ({ playground }) => {
34
+ await playground.goto();
35
+ await playground.click('tabTriggerInputs');
36
+ await playground.fillInputForm('Hello World', 'secret123', '42');
37
+ await playground.fill('playgroundTextarea', 'This is a test message');
38
+ await playground.fillOtp('654321');
39
+ }
40
+ );
@@ -0,0 +1,49 @@
1
+ import { registry as test } from "@pages/registry";
2
+
3
+ // Serial — all tests run sequentially in one browser, sharing the same worker page
4
+ test.describe.serial('Playground Serial Suite', () => {
5
+ test.beforeAll(async ({ workerPlayground }) => {
6
+ await workerPlayground.goto();
7
+ })
8
+
9
+ test(
10
+ 'Navigate to playground and interact with input elements',
11
+ async ({ workerPlayground }) => {
12
+ await workerPlayground.click('tabTriggerInputs');
13
+
14
+ await workerPlayground.fill('playgroundText', 'Serial Input');
15
+ await workerPlayground.fill('playgroundPassword', 'pass123');
16
+ await workerPlayground.fill('playgroundNumber', '99');
17
+ await workerPlayground.fill('playgroundTextarea', 'Worker-scoped textarea content');
18
+
19
+ await workerPlayground.click('playgroundSwitch');
20
+ await workerPlayground.check('Accept terms');
21
+ }
22
+ );
23
+
24
+ test(
25
+ 'Switch to buttons tab and interact with button variants',
26
+ async ({ workerPlayground }) => {
27
+ await workerPlayground.click('tabTriggerButtons');
28
+
29
+ await workerPlayground.click('btnVariantDefault');
30
+ await workerPlayground.click('btnVariantSecondary');
31
+ await workerPlayground.click('btnVariantOutline');
32
+ await workerPlayground.click('btnVariantDestructive');
33
+
34
+ await workerPlayground.click('toggleAlignCenter');
35
+ await workerPlayground.click('toggleAlignRight');
36
+ await workerPlayground.click('toggleAlignLeft');
37
+ }
38
+ );
39
+
40
+ test(
41
+ 'Browse remaining tabs on the shared worker page',
42
+ async ({ workerPlayground }) => {
43
+ await workerPlayground.click('tabTriggerTables');
44
+ await workerPlayground.click('tabTriggerCharts');
45
+ await workerPlayground.click('tabTriggerOverlays');
46
+ await workerPlayground.click('tabTriggerAdvanced');
47
+ }
48
+ );
49
+ });
@@ -1,16 +1,14 @@
1
- import dotenv from 'dotenv';
2
- import path from 'path';
1
+ import dotenv from 'dotenv'
2
+ import path from 'path'
3
3
 
4
4
  // Load env configuration
5
- dotenv.config({ path: path.resolve('.env'), quiet: true });
5
+ dotenv.config({ path: path.resolve('.env'), quiet: true })
6
6
 
7
7
  export const ENV = {
8
8
  url: process.env.URL || 'https://qecore.github.io',
9
9
  testUser: process.env.TEST_USER || 'default',
10
- testPassword: process.env.TEST_PASSWORD || 'secret',
11
- } as const;
12
-
10
+ testPassword: process.env.TEST_PASSWORD || 'secret'
11
+ } as const
13
12
 
14
13
  // It's always best to prefer typesafe variables for .env
15
14
  export const env = ENV
16
-
@@ -1,7 +1,7 @@
1
- import { ProjectsPage } from "@pages/projects.page";
2
- import { registry } from "@pages/registry";
1
+ import { registry } from '../pages/registry';
2
+ import { PlaygroundPage } from '../pages/playground.page';
3
3
 
4
- // Extend registry with overridden ProjectsPage class
4
+ // Extend base registry with overridden class
5
5
  export const scenario = registry.extend({
6
- projectsPage: ProjectsPage,
6
+ playground: PlaygroundPage
7
7
  });
@@ -9,12 +9,18 @@
9
9
  "noEmit": true,
10
10
  "baseUrl": ".",
11
11
  "paths": {
12
- "@pages/*": ["src/pages/*"],
13
- "@tests/*": ["src/tests/*"],
14
- "@utils/*": ["src/utils/*"]
12
+ "@pages/*": [
13
+ "src/pages/*"
14
+ ],
15
+ "@tests/*": [
16
+ "src/tests/*"
17
+ ],
18
+ "@utils/*": [
19
+ "src/utils/*"
20
+ ]
15
21
  }
16
22
  },
17
23
  "include": [
18
24
  "**/*.ts"
19
25
  ]
20
- }
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pw-core",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Initialize a pw-core test suite in a project",
5
5
  "bin": {
6
6
  "create-pw-core": "dist/index.js"
@@ -1,29 +0,0 @@
1
- import type { Page } from "@playwright/test";
2
- import { Table } from "pw-core/component/table";
3
- import { registry } from "@pages/registry";
4
-
5
- type TableType = { title: string }
6
-
7
- // To Override the page config to add custom methods for the page
8
- export class ProjectsPage extends registry.pages.projectsPage {
9
- projectsTable = new Table<TableType>(this.table);
10
-
11
- constructor(page: Page) {
12
- super(page);
13
- }
14
-
15
- async createProject(title: string, description: string) {
16
- await this.click('newProject');
17
- await this.fill('formTitle', title);
18
- await this.fill('formDescription', description);
19
- await this.click('formSave');
20
- }
21
-
22
- async verifyProjectInTable(title: string) {
23
- const rows = await this.projectsTable.get();
24
- const titles = rows.getAll('title');
25
- if (!titles.includes(title)) {
26
- throw new Error(`Project "${title}" not found in projects table.`);
27
- }
28
- }
29
- }
@@ -1,42 +0,0 @@
1
- import { expect } from '@playwright/test';
2
- import { scenario } from '@utils/fixtures';
3
-
4
- scenario('Verify advanced locators, actions and assertions in pw-core', async ({
5
- loginPage,
6
- dashboardPage,
7
- projectsPage,
8
- sidebar
9
- }) => {
10
- // 1. Navigation and Title Verification
11
- await loginPage.goto();
12
- await loginPage.verifyTitle('PW-Core Workspace — Build, Test & Document');
13
- await loginPage.waitForLoadState('networkidle');
14
-
15
- // 2. Click action using options (hasText)
16
- // This resolves the locator and filters it by the provided text before clicking
17
- await loginPage.click('defaultUserLogin', { hasText: /Default/ });
18
- await dashboardPage.verifyURL();
19
-
20
- // 3. All verify methods (using default visibility checks)
21
- await dashboardPage.verify('heading'); // Inbuilt visibility check
22
- await loginPage.verifyHidden('defaultUserLogin'); // verifyHidden asserts that the element is hidden
23
-
24
- // Navigate to Projects Page
25
- await sidebar.click('itemProjects');
26
- await projectsPage.verifyURL();
27
- await projectsPage.verifyEnabled('newProject'); // verifyEnabled asserts element is enabled
28
-
29
- // 4. Action click/fill using options (nth)
30
- await projectsPage.click('newProject', { nth: 0 });
31
- await projectsPage.fill('formTitle', 'Form Title Nth Test', { nth: 0 });
32
- await projectsPage.fill('formDescription', 'Form Description Nth Test', { nth: 0 });
33
-
34
- // Verify disabled state using verifyEnabled
35
- await projectsPage.verifyEnabled('formSave');
36
- await projectsPage.click('formSave');
37
-
38
- // 5. Locator chaining on typed Page objects
39
- // Resolves the parent 'table' locator and chains standard Playwright locator methods on it
40
- const projectTableRows = projectsPage.locator('table').locator('tbody tr').first();
41
- await expect(projectTableRows).toBeVisible();
42
- });
@@ -1,64 +0,0 @@
1
- import { expect } from '@playwright/test';
2
- import { Table } from 'pw-core/component/table';
3
- import { scenario } from '@utils/fixtures';
4
-
5
- scenario('End-to-End User Flow on QECore App with Page Object Flows', async ({
6
- loginPage,
7
- dashboardPage,
8
- projectsPage,
9
- tasksPage,
10
- sidebar,
11
- topNav
12
- }) => {
13
- // 1. Login (automatic page usage)
14
- await loginPage.goto();
15
- await loginPage.waitForLoadState('networkidle');
16
-
17
- // Verify title and page element states
18
- await loginPage.verifyTitle(/PW-Core/);
19
- await loginPage.verify('defaultUserLogin').toBeEnabled();
20
-
21
- await loginPage.click('defaultUserLogin');
22
- await dashboardPage.verifyURL();
23
-
24
- // Verify dashboard page is loaded with soft assertions (toBeVisible is default and doesn't need to be chained)
25
- await dashboardPage.verify.soft('heading');
26
- await dashboardPage.verify.soft('heading').toHaveText('Dashboard');
27
-
28
- // 2. Create a Project (using custom overridden ProjectsPage flows)
29
- await sidebar.click('itemProjects');
30
- await projectsPage.verifyURL();
31
-
32
- // Verify elements are not present initially using verifyHidden
33
- await projectsPage.verifyHidden('formTitle');
34
-
35
- await projectsPage.createProject('Demo Project', 'A project created via pw-core automation');
36
- await projectsPage.verifyProjectInTable('Demo Projecter');
37
-
38
- // 3. Create a Task (automatic page usage, creating Table component inline)
39
- await sidebar.click('itemTasks');
40
- await tasksPage.verifyURL();
41
- await tasksPage.click('newTask');
42
-
43
- // Verify element attributes using the typed expect wrapper
44
- await tasksPage.expect('formTitle').toBeVisible();
45
-
46
- await tasksPage.fill('formTitle', 'Demo Task');
47
- await tasksPage.fill('formDescription', 'A task created via pw-core automation.');
48
- await tasksPage.click('formSave');
49
-
50
- // Wait for the new task to appear in the table using built-in verify
51
- await tasksPage.verify('table', { hasText: 'Demo Task' });
52
-
53
- const taskTable = new Table<{ title: string }>(tasksPage.table);
54
- const taskHeaders = await taskTable.getHeaders();
55
- expect(taskHeaders).toContain('title');
56
- const taskRows = await taskTable.get();
57
- expect(taskRows.getAll('title')).toContain('Demo Task');
58
-
59
- // 4. Logout (automatic page usage)
60
- await topNav.hover('workspaceDropdown');
61
- await topNav.click('logoutBtn');
62
- await loginPage.verifyURL();
63
- await loginPage.verify('defaultUserLogin'); // Inbuilt visibility check
64
- });
@@ -1,17 +0,0 @@
1
- import { scenario } from '@utils/fixtures';
2
-
3
- scenario('Verify password masking in step descriptions during login flow', async ({ loginPage, dashboardPage }) => {
4
- await loginPage.page.goto("http://localhost:5173/login");
5
-
6
- // Fill email (should not be masked in logs/reports)
7
- await loginPage.fill('email', 'default@mail.com');
8
-
9
- // Fill password (should be masked in logs/reports)
10
- await loginPage.fill('password', 'default');
11
-
12
- // Click login
13
- await loginPage.click('submit');
14
-
15
- // Verify dashboard loaded
16
- await dashboardPage.verifyURL();
17
- });
@@ -1,65 +0,0 @@
1
- import { scenario as baseScenario } from '@utils/fixtures';
2
- import { Page, Browser, BrowserContext } from '@playwright/test';
3
-
4
- type Session = {
5
- context: BrowserContext;
6
- page: Page;
7
- } & {
8
- [K in keyof typeof baseScenario.pages]: InstanceType<(typeof baseScenario.pages)[K]>;
9
- };
10
-
11
- // Helper to create an isolated user session with all page objects instantiated
12
- async function createUser(browser: Browser): Promise<Session> {
13
- const context = await browser.newContext();
14
- const page = await context.newPage();
15
-
16
- const pages: any = {};
17
- for (const [key, PageClass] of Object.entries(baseScenario.pages)) {
18
- pages[key] = new (PageClass as any)(page);
19
- }
20
-
21
- return {
22
- context,
23
- page,
24
- ...pages
25
- } as Session;
26
- }
27
-
28
- // Extend the imported scenario locally with custom user session fixtures
29
- const scenario = baseScenario.extend<{
30
- user1: Session;
31
- user2: Session;
32
- }>({
33
- user1: async ({ browser }, use) => {
34
- const session = await createUser(browser);
35
- await use(session);
36
- await session.context.close();
37
- },
38
-
39
- user2: async ({ browser }, use) => {
40
- const session = await createUser(browser);
41
- await use(session);
42
- await session.context.close();
43
- },
44
- });
45
-
46
- /**
47
- * NOTE: This is a simulation of multiple isolated contexts/sessions in a single test,
48
- * not real multi-user authentication. You can follow this pattern for seamless multi-user flows.
49
- */
50
- scenario('Verify multi-user concurrent context control', async ({ user1, user2 }) => {
51
- // Both navigate to login page
52
- await user1.loginPage.goto();
53
- await user2.loginPage.goto();
54
-
55
- // User 1 (Admin) logs in
56
- await user1.loginPage.click('defaultUserLogin');
57
- await user1.dashboardPage.verifyURL();
58
- await user1.dashboardPage.verify('heading').toHaveText('Dashboard');
59
-
60
- // User 2 logs in independently
61
- await user2.loginPage.click('defaultUserLogin');
62
- await user2.dashboardPage.verifyURL();
63
- await user2.dashboardPage.verify('heading').toHaveText('Dashboard');
64
- });
65
-
@@ -1,29 +0,0 @@
1
- import { scenario } from '@utils/fixtures';
2
-
3
- scenario('Verify the playground - chart elements are visible', async ({ playground, dashboardPage }) => {
4
- // Navigation and tab setup
5
- // await playground.goto();
6
- await playground.page.goto("http://localhost:5173/playground");
7
-
8
- await playground.waitForLoadState('networkidle');
9
-
10
- // Click on the "Charts" tab to bring the chart components into view
11
- await playground.page.getByRole('tab', { name: 'Charts' }).click();
12
-
13
- // Verify dynamic selectors
14
- await playground.verify('safe');
15
- await playground.verify('danger');
16
-
17
- // Verify by dynamic testIds
18
- await playground.verify('activeLineChart');
19
- await playground.verify('inactiveLineChart').toBeDisabled();
20
-
21
- // Verify by chained locators + dynamic locator
22
- await playground.verify('card.activeBarChart');
23
- await playground.verifyDisabled('card.inactiveBarChart')
24
- await playground.verify('card.safe');
25
- });
26
-
27
-
28
-
29
-
@@ -1,23 +0,0 @@
1
- import { scenario } from '@utils/fixtures';
2
-
3
- scenario.describe.serial('Worker Page State Reuse Suite', () => {
4
- // Scenario 1: Setup state inside the workerPage
5
- scenario('Test 1: Navigate and transition page state on workerPage', async ({ workerLoginPage: lp, workerDashboardPage: dp }) => {
6
- await lp.goto();
7
- await lp.waitForLoadState('networkidle');
8
- await lp.verifyURL();
9
- await lp.verify('defaultUserLogin');
10
-
11
- // Perform action that transitions page state (Login)
12
- await lp.click('defaultUserLogin');
13
- await dp.verifyURL({ timeout: 2000 });
14
- });
15
-
16
- // Scenario 2: Verify same state persists in a subsequent test in the same worker,
17
- // without requesting the page-scoped `page` or `loginPage` fixtures at all.
18
- scenario('Test 2: Verify same page instance and state persist on workerPage', async ({ workerDashboardPage: dp }) => {
19
- // Verify using workerDashboardPage fixture (which is bound to workerPage)
20
- await dp.verifyURL();
21
- await dp.verify('heading');
22
- });
23
- });
@@ -1,43 +0,0 @@
1
- import { expect } from '@playwright/test';
2
- import { scenario } from '@utils/fixtures';
3
- import {
4
- getLocalStorage,
5
- setLocalStorage,
6
- getSessionStorage,
7
- setSessionStorage,
8
- seedSessionStorage
9
- } from 'pw-core/helpers';
10
-
11
- scenario('Verify local and session storage helpers', async ({ page }) => {
12
- await page.goto('/login');
13
-
14
- // 1. LocalStorage Helpers
15
- await setLocalStorage(page, 'localKey', 'localValue');
16
- const localVal = await getLocalStorage(page, 'localKey');
17
- expect(localVal).toBe('localValue');
18
-
19
- // 2. SessionStorage Helpers
20
- await setSessionStorage(page, 'sessionKey', 'sessionValue');
21
- const sessionVal = await getSessionStorage(page, 'sessionKey');
22
- expect(sessionVal).toBe('sessionValue');
23
- });
24
-
25
- scenario('Verify sessionStorage seeding helper', async ({ page }) => {
26
- // Add a cookie to simulate authenticated state and trigger sessionStorage seeding
27
- await page.context().addCookies([{
28
- name: 'auth-token',
29
- value: 'dummy-token-value',
30
- domain: 'qecore.github.io',
31
- path: '/'
32
- }]);
33
-
34
- // Seed sessionStorage before load/navigation
35
- await seedSessionStorage(page, { seededKey: 'seededValue' });
36
-
37
- await page.goto('/login');
38
-
39
- const seededVal = await getSessionStorage(page, 'seededKey');
40
- expect(seededVal).toBe('seededValue');
41
-
42
- console.log('Successfully seeded and retrieved sessionStorage value:', seededVal);
43
- });