@sridharkikkeri/playwright-common 1.0.46 ā 1.0.48
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/create-healthedge-tests.js +60 -35
- package/package.json +2 -1
- package/templates/DashboardPage.ts +22 -0
- package/templates/LoginPage.ts +34 -0
- package/templates/UserBuilder.ts +43 -0
- package/templates/fixtures.ts +73 -0
- package/templates/sample.spec.ts +39 -0
|
@@ -76,7 +76,8 @@ const packageJson = {
|
|
|
76
76
|
dependencies: {
|
|
77
77
|
'@playwright/test': '^1.42.0',
|
|
78
78
|
'allure-playwright': '^3.4.5',
|
|
79
|
-
'playwright': '^1.42.0'
|
|
79
|
+
'playwright': '^1.42.0',
|
|
80
|
+
'@playwright/mcp-server': 'latest'
|
|
80
81
|
},
|
|
81
82
|
devDependencies: {
|
|
82
83
|
'@typescript-eslint/eslint-plugin': '^8.55.0',
|
|
@@ -164,41 +165,47 @@ Object.entries(envConfigs).forEach(([env, config]) => {
|
|
|
164
165
|
);
|
|
165
166
|
});
|
|
166
167
|
|
|
167
|
-
//
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
168
|
+
// Copy sample files from templates
|
|
169
|
+
const templatesDir = path.join(__dirname, 'templates');
|
|
170
|
+
if (fs.existsSync(templatesDir)) {
|
|
171
|
+
console.log('\nš Copying sample files...\n');
|
|
172
|
+
|
|
173
|
+
// Copy fixtures
|
|
174
|
+
const fixturesSrc = path.join(templatesDir, 'fixtures.ts');
|
|
175
|
+
if (fs.existsSync(fixturesSrc)) {
|
|
176
|
+
fs.copyFileSync(fixturesSrc, path.join(projectPath, 'src/main/fixtures/fixtures.ts'));
|
|
177
|
+
console.log(' ā Copied fixtures.ts');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Copy pages
|
|
181
|
+
const loginPageSrc = path.join(templatesDir, 'LoginPage.ts');
|
|
182
|
+
if (fs.existsSync(loginPageSrc)) {
|
|
183
|
+
fs.copyFileSync(loginPageSrc, path.join(projectPath, 'src/main/pages/LoginPage.ts'));
|
|
184
|
+
console.log(' ā Copied LoginPage.ts');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const dashboardPageSrc = path.join(templatesDir, 'DashboardPage.ts');
|
|
188
|
+
if (fs.existsSync(dashboardPageSrc)) {
|
|
189
|
+
fs.copyFileSync(dashboardPageSrc, path.join(projectPath, 'src/main/pages/DashboardPage.ts'));
|
|
190
|
+
console.log(' ā Copied DashboardPage.ts');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Copy builder
|
|
194
|
+
const builderSrc = path.join(templatesDir, 'UserBuilder.ts');
|
|
195
|
+
if (fs.existsSync(builderSrc)) {
|
|
196
|
+
fs.copyFileSync(builderSrc, path.join(projectPath, 'src/main/builders/UserBuilder.ts'));
|
|
197
|
+
console.log(' ā Copied UserBuilder.ts');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Copy test
|
|
201
|
+
const testSrc = path.join(templatesDir, 'sample.spec.ts');
|
|
202
|
+
if (fs.existsSync(testSrc)) {
|
|
203
|
+
fs.copyFileSync(testSrc, path.join(projectPath, 'src/tests/specs/sample.spec.ts'));
|
|
204
|
+
console.log(' ā Copied sample.spec.ts');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log('\nā
Sample files copied');
|
|
183
208
|
}
|
|
184
|
-
`;
|
|
185
|
-
|
|
186
|
-
fs.writeFileSync(path.join(projectPath, 'src/main/pages/HomePage.ts'), samplePage);
|
|
187
|
-
|
|
188
|
-
// Sample Test
|
|
189
|
-
const sampleTest = `import { test } from '../../../main/fixtures/fixtures';
|
|
190
|
-
import { HomePage } from '../../main/pages/HomePage';
|
|
191
|
-
|
|
192
|
-
test.describe('Sample Tests', () => {
|
|
193
|
-
test('Search functionality', async ({ page }) => {
|
|
194
|
-
const homePage = new HomePage(page);
|
|
195
|
-
await page.goto('/');
|
|
196
|
-
await homePage.search('playwright');
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
`;
|
|
200
|
-
|
|
201
|
-
fs.writeFileSync(path.join(projectPath, 'src/tests/specs/sample.spec.ts'), sampleTest);
|
|
202
209
|
|
|
203
210
|
// Test data
|
|
204
211
|
const testData = {
|
|
@@ -295,6 +302,24 @@ npm run screenshot <url> # Take screenshot of URL
|
|
|
295
302
|
npm run pdf <url> # Generate PDF of URL
|
|
296
303
|
\`\`\`
|
|
297
304
|
|
|
305
|
+
## AI Agents & Skills (Experimental)
|
|
306
|
+
|
|
307
|
+
This project includes Playwright MCP Server with AI Agents:
|
|
308
|
+
|
|
309
|
+
**Agents:**
|
|
310
|
+
- š¤ **Planner** - Plans test scenarios
|
|
311
|
+
- š§ **Generator** - Generates test code
|
|
312
|
+
- 𩹠**Healer** - Auto-fixes failing tests
|
|
313
|
+
|
|
314
|
+
**Skills:**
|
|
315
|
+
- Run tests
|
|
316
|
+
- Inspect DOM
|
|
317
|
+
- Generate selectors
|
|
318
|
+
- Heal failures
|
|
319
|
+
- Seed environment
|
|
320
|
+
|
|
321
|
+
See [Playwright CLI Agents](https://github.com/microsoft/playwright-cli) for setup.
|
|
322
|
+
|
|
298
323
|
## Environment-Specific Tests
|
|
299
324
|
|
|
300
325
|
\`\`\`bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sridharkikkeri/playwright-common",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.48",
|
|
4
4
|
"description": "Production-grade Playwright framework with AI-powered self-healing, visual regression, and enterprise features",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
15
|
"src",
|
|
16
|
+
"templates",
|
|
16
17
|
"api-docs",
|
|
17
18
|
"create-healthedge-tests.js",
|
|
18
19
|
"README.md",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Page } from '@playwright/test';
|
|
2
|
+
import { BasePage } from '../main/pages/BasePage';
|
|
3
|
+
import { ElementWrapper } from '../main/wrappers/ElementWrapper';
|
|
4
|
+
|
|
5
|
+
export class DashboardPage extends BasePage {
|
|
6
|
+
readonly welcomeMessage: ElementWrapper;
|
|
7
|
+
readonly logoutButton: ElementWrapper;
|
|
8
|
+
|
|
9
|
+
constructor(page: Page) {
|
|
10
|
+
super(page, { pageName: 'DashboardPage' });
|
|
11
|
+
this.welcomeMessage = this.getByTestId('welcome-message');
|
|
12
|
+
this.logoutButton = this.getByRole('button', { name: 'Logout' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async getWelcomeText(): Promise<string> {
|
|
16
|
+
return await this.welcomeMessage.textContent();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async logout(): Promise<void> {
|
|
20
|
+
await this.logoutButton.click('Click logout button');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Page } from '@playwright/test';
|
|
2
|
+
import { BasePage } from '../main/pages/BasePage';
|
|
3
|
+
import { ElementWrapper } from '../main/wrappers/ElementWrapper';
|
|
4
|
+
|
|
5
|
+
export class LoginPage extends BasePage {
|
|
6
|
+
readonly emailInput: ElementWrapper;
|
|
7
|
+
readonly passwordInput: ElementWrapper;
|
|
8
|
+
readonly loginButton: ElementWrapper;
|
|
9
|
+
|
|
10
|
+
constructor(page: Page) {
|
|
11
|
+
super(page, { pageName: 'LoginPage' });
|
|
12
|
+
this.emailInput = this.getByLabel('Email');
|
|
13
|
+
this.passwordInput = this.getByLabel('Password');
|
|
14
|
+
this.loginButton = this.getByRole('button', { name: 'Login' });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async enterEmail(email: string): Promise<void> {
|
|
18
|
+
await this.emailInput.fill(email, 'Enter email address');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async enterPassword(password: string): Promise<void> {
|
|
22
|
+
await this.passwordInput.fill(password, 'Enter password');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async clickLogin(): Promise<void> {
|
|
26
|
+
await this.loginButton.click('Click login button');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async login(email: string, password: string): Promise<void> {
|
|
30
|
+
await this.enterEmail(email);
|
|
31
|
+
await this.enterPassword(password);
|
|
32
|
+
await this.clickLogin();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface UserData {
|
|
2
|
+
firstName: string;
|
|
3
|
+
lastName: string;
|
|
4
|
+
email?: string;
|
|
5
|
+
phone?: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class UserBuilder {
|
|
10
|
+
private data: UserData = {
|
|
11
|
+
firstName: 'John',
|
|
12
|
+
lastName: 'Doe'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
setFirstName(name: string): this {
|
|
16
|
+
this.data.firstName = name;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setLastName(name: string): this {
|
|
21
|
+
this.data.lastName = name;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setEmail(email: string): this {
|
|
26
|
+
this.data.email = email;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setPhone(phone: string): this {
|
|
31
|
+
this.data.phone = phone;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setRole(role: string): this {
|
|
36
|
+
this.data.role = role;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
build(): UserData {
|
|
41
|
+
return { ...this.data };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { test as base } from '@playwright/test';
|
|
2
|
+
import { ActionOrchestrator } from '../main/selfhealing/ActionOrchestrator';
|
|
3
|
+
import { ApiClient } from '../main/api/ApiClient';
|
|
4
|
+
import { AllureUtil } from '../main/reporting/AllureUtil';
|
|
5
|
+
import { LoginPage } from '../main/pages/LoginPage';
|
|
6
|
+
import { DashboardPage } from '../main/pages/DashboardPage';
|
|
7
|
+
|
|
8
|
+
export type FrameworkFixtures = {
|
|
9
|
+
orchestrator: ActionOrchestrator;
|
|
10
|
+
apiClient: ApiClient;
|
|
11
|
+
loginPage: LoginPage;
|
|
12
|
+
dashboardPage: DashboardPage;
|
|
13
|
+
locale: string;
|
|
14
|
+
retryInfo: { current: number; max: number };
|
|
15
|
+
_reportingHook: void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const test = base.extend<FrameworkFixtures>({
|
|
19
|
+
orchestrator: async ({ page }, use, testInfo) => {
|
|
20
|
+
const orchestrator = new ActionOrchestrator(page, {
|
|
21
|
+
pageName: testInfo.title
|
|
22
|
+
});
|
|
23
|
+
await use(orchestrator);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
apiClient: async ({ }, use) => {
|
|
27
|
+
const client = new ApiClient(process.env.API_BASE_URL || '');
|
|
28
|
+
await client.init();
|
|
29
|
+
await use(client);
|
|
30
|
+
await client.dispose();
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
loginPage: async ({ page }, use) => {
|
|
34
|
+
await use(new LoginPage(page));
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
dashboardPage: async ({ page }, use) => {
|
|
38
|
+
await use(new DashboardPage(page));
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
locale: async ({ }, use) => {
|
|
42
|
+
const locale = process.env.LOCALE || 'en';
|
|
43
|
+
await use(locale);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
retryInfo: async ({ }, use, testInfo) => {
|
|
47
|
+
await use({
|
|
48
|
+
current: testInfo.retry,
|
|
49
|
+
max: testInfo.project.retries
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
_reportingHook: [async ({ retryInfo }, use, testInfo) => {
|
|
54
|
+
await use();
|
|
55
|
+
|
|
56
|
+
if (testInfo.status !== testInfo.expectedStatus) {
|
|
57
|
+
AllureUtil.attachText(
|
|
58
|
+
'Test Failure Details',
|
|
59
|
+
`Error: ${testInfo.error?.message || 'Unknown'}\nStack: ${testInfo.error?.stack || 'N/A'}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (retryInfo.current > 0) {
|
|
64
|
+
AllureUtil.attachText(
|
|
65
|
+
'Retry Telemetry',
|
|
66
|
+
`This test is being retried. Attempt: ${retryInfo.current} / ${retryInfo.max}`
|
|
67
|
+
);
|
|
68
|
+
console.warn(`ā ļø Flaky test detected: "${testInfo.title}" (Retry ${retryInfo.current})`);
|
|
69
|
+
}
|
|
70
|
+
}, { auto: true }]
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export { expect } from '@playwright/test';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { test, expect } from '../main/fixtures/fixtures';
|
|
2
|
+
import { UserBuilder } from '../main/builders/UserBuilder';
|
|
3
|
+
import { ConfigManager } from '../main/config/ConfigManager';
|
|
4
|
+
|
|
5
|
+
test.describe('Sample Test Suite @smoke', () => {
|
|
6
|
+
test('Login with valid credentials', async ({ page, loginPage, dashboardPage }) => {
|
|
7
|
+
const config = ConfigManager.getConfig();
|
|
8
|
+
await page.goto(config.baseUrl!);
|
|
9
|
+
await page.waitForLoadState('networkidle');
|
|
10
|
+
|
|
11
|
+
await loginPage.login('test@example.com', 'password123');
|
|
12
|
+
await page.waitForLoadState('networkidle');
|
|
13
|
+
|
|
14
|
+
const welcomeText = await dashboardPage.getWelcomeText();
|
|
15
|
+
expect(welcomeText).toContain('Welcome');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('Create user with builder pattern', async ({ page, loginPage }) => {
|
|
19
|
+
const timestamp = Date.now();
|
|
20
|
+
const user = new UserBuilder()
|
|
21
|
+
.setFirstName('Test')
|
|
22
|
+
.setLastName(`User-${timestamp}`)
|
|
23
|
+
.setEmail(`test-${timestamp}@example.com`)
|
|
24
|
+
.setRole('admin')
|
|
25
|
+
.build();
|
|
26
|
+
|
|
27
|
+
console.log('Created user:', user);
|
|
28
|
+
|
|
29
|
+
const config = ConfigManager.getConfig();
|
|
30
|
+
await page.goto(config.baseUrl!);
|
|
31
|
+
await loginPage.login(user.email!, 'password123');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('API integration example', async ({ apiClient }) => {
|
|
35
|
+
const response = await apiClient.get('/api/users/1');
|
|
36
|
+
expect(response.status).toBe(200);
|
|
37
|
+
expect(response.data).toHaveProperty('id');
|
|
38
|
+
});
|
|
39
|
+
});
|