Skip to content

Commit 3e65679

Browse files
committed
playwright test for product
1 parent e5ea3c1 commit 3e65679

File tree

14 files changed

+464
-77
lines changed

14 files changed

+464
-77
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,4 @@ App_Data/
165165
*.db
166166
/src/ZKEACMS.WebHost/Temp
167167
/src/ZKEACMS.WebHost/Logs
168+
.env

test/End-To-End/.env

Lines changed: 0 additions & 2 deletions
This file was deleted.

test/End-To-End/.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1-
# Environment variables for ZKEACMS End-to-End Tests
1+
# Environment Configuration
2+
Base_URL = http://localhost:5000
3+
4+
# Admin Credentials
25
ADMIN_USERNAME=admin
36
ADMIN_PASSWORD=admin
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
description="create a playwright page object model"
2+
3+
prompt = """
4+
You are an expert in creating Playwright page object models for the ZKEACMS project. Your task is to generate a complete TypeScript page object model file based on a provided URL.
5+
6+
Here's what you should do:
7+
8+
1. **Open and Analyze the URL**: Use Playwright(playwright/browser_navigate tool)to open the user-provided URL and carefully analyze the page structure, elements, and functionality.
9+
- If the page require login, you can get user name, password from file @.env
10+
11+
2. **Identify Page Type**: Determine if this is a frontend (public) page or a backend (admin) page:
12+
- Frontend pages are typically public-facing pages like homepages, article pages, product pages, etc.
13+
- Backend pages are admin pages found under /admin/ URLs, including forms, dashboards, management interfaces.
14+
15+
3. **Select Base Class**:
16+
- For backend (admin) pages, extend the `AdminPageBase` class
17+
- For frontend pages, extend the generic `PageBase` class or create without a base class if not needed
18+
19+
4. **Create the Page Object Model File**:
20+
- Generate a complete TypeScript file that includes:
21+
* Proper imports for Playwright and base classes
22+
* A class that represents the page with a descriptive name
23+
* Constructor that accepts a Playwright Page object
24+
* Public methods that represent key actions on the page (clicks, fills, navigations)
25+
* Locator properties for important elements on the page
26+
* Get the page html (use javascript `document.body.innerHTML` to get page html) then you can get the best locators:
27+
- Prioritize data-testid attributes: page.locator('[data-testid="button"]')
28+
- Use semantic selectors: page.getByRole('button', { name: 'Submit' })
29+
- Use text content: page.getByText('Save')
30+
- Use CSS selectors sparingly: page.locator('#id', '.class')
31+
- Use nth() for selecting elements by index: page.locator('.item').nth(0)
32+
- Use hasText() for filtering by text: page.locator('.item', { hasText: 'Item 1' })
33+
- Use has() for filtering by child elements: page.locator('.item', { has: page.locator('img') })
34+
- **Do not** use '[ref="exx"]': page.locator('[ref="e78"]')
35+
* Methods that support the main functionality of the page
36+
37+
5. **Follow Project Conventions**:
38+
- Use descriptive names for methods and properties
39+
- Follow the existing naming patterns in the ZKEACMS project
40+
- Include proper documentation comments for public methods
41+
- Implement proper error handling where needed
42+
- Use async/await pattern for Playwright operations
43+
44+
6. **Page Object Best Practices**:
45+
- Encapsulate the details of how to interact with page elements
46+
- Return appropriate values from methods (e.g., other page objects for navigations)
47+
- Keep methods focused on single responsibilities
48+
- Use locators that are stable and reliable
49+
50+
7. **File Location**: Place the generated file in the appropriate directory:
51+
- Admin pages go in src/admin/
52+
- Frontend pages go in src/models/ or a relevant subdirectory
53+
54+
EXAMPLE STRUCTURE:
55+
56+
For a frontend page:
57+
```
58+
import { Page } from '@playwright/test';
59+
import { PageBase } from '@models/PageBase';
60+
61+
export class HomePage extends PageBase {
62+
63+
constructor(page: Page) {
64+
super(page);
65+
}
66+
67+
async navigateTo(): Promise<void> {
68+
await this.page.goto('/index');
69+
}
70+
}
71+
```
72+
73+
For an admin page:
74+
```
75+
import { Page } from '@playwright/test';
76+
import { AdminPageBase } from "@models/AdminPageBase";
77+
78+
export class ArticleManagementPage extends AdminPageBase {
79+
80+
constructor(page: Page) {
81+
super(page);
82+
}
83+
84+
async navigateTo(): Promise<void> {
85+
await this.page.goto('/admin/article');
86+
}
87+
88+
}
89+
```
90+
91+
Analyze the page provided by the user, determine the page type, and create the appropriate page object model file.
92+
"""
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"mcpServers": {
3+
"playwright": {
4+
"command": "npx",
5+
"args": [
6+
"@playwright/mcp@latest",
7+
"--browser=chromium"
8+
]
9+
}
10+
}
11+
}

test/End-To-End/.qwen/commands/page-model.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ You are an expert in creating Playwright page object models for the ZKEACMS proj
55
66
Here's what you should do:
77
8-
1. **Open and Analyze the URL(Use playwright/browser_navigate tool)**: Use Playwright to open the user-provided URL and carefully analyze the page structure, elements, and functionality.
8+
1. **Open and Analyze the URL**: Use Playwright(playwright/browser_navigate tool)to open the user-provided URL and carefully analyze the page structure, elements, and functionality.
99
- If the page require login, you can get user name, password from file @.env
1010
1111
2. **Identify Page Type**: Determine if this is a frontend (public) page or a backend (admin) page:

test/End-To-End/playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default defineConfig({
2626
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
2727
use: {
2828
/* Base URL to use in actions like `await page.goto('')`. */
29-
baseURL: 'http://localhost:5000',
29+
baseURL: process.env.Base_URL || 'http://localhost:5000',
3030

3131
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
3232
trace: 'on-first-retry',

test/End-To-End/src/admin/ArticleFormPage.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,35 +99,36 @@ export class ArticleFormPage extends AdminPageBase {
9999
status?: string,
100100
description?: string
101101
}): Promise<void> {
102-
if (articleData.title) await this.titleField.fill(articleData.title);
103-
if (articleData.url) await this.urlField.fill(articleData.url);
104-
if (articleData.summary) await this.summaryField.fill(articleData.summary);
105-
if (articleData.metaKeywords) await this.metaKeywordsField.fill(articleData.metaKeywords);
106-
if (articleData.metaDescription) await this.metaDescriptionField.fill(articleData.metaDescription);
107-
if (articleData.counter) await this.counterField.fill(articleData.counter);
108-
if (articleData.content) await this.page.locator('#ArticleContent_ifr').contentFrame().locator('#tinymce').fill(articleData.content);
109-
if (articleData.imageThumbUrl) await this.imageThumbUrlField.fill(articleData.imageThumbUrl);
110-
if (articleData.imageUrl) await this.imageUrlField.fill(articleData.imageUrl);
111-
if (articleData.publishDate) await this.publishDateField.fill(articleData.publishDate);
112-
if (articleData.description) await this.descriptionField.fill(articleData.description);
102+
await this.fill(this.titleField, articleData.title);
103+
await this.fill(this.urlField, articleData.url);
104+
await this.fill(this.summaryField, articleData.summary);
105+
await this.fill(this.metaKeywordsField, articleData.metaKeywords);
106+
await this.fill(this.metaDescriptionField, articleData.metaDescription);
107+
await this.fill(this.counterField, articleData.counter);
108+
await this.fillTinymceEditor(this.page.locator('#ArticleContent_ifr'), articleData.content);
109+
await this.fill(this.imageThumbUrlField, articleData.imageThumbUrl);
110+
await this.fill(this.imageUrlField, articleData.imageUrl);
111+
await this.fill(this.publishDateField, articleData.publishDate);
112+
await this.fill(this.descriptionField, articleData.description);
113113

114114
// Select article type if provided
115115
if (articleData.articleType) {
116116
await this.selectArticleType(articleData.articleType);
117117
}
118118

119119
// Select status if provided
120-
if (articleData.status) {
121-
await this.statusDropdown.selectOption(articleData.status);
122-
}
120+
await this.fill(this.statusDropdown, articleData.status);
123121
}
124122

125123
/**
126124
* Selects an article type from the dropdown tree
127125
* @param articleType The article type to select
128126
*/
129-
async selectArticleType(articleType: string): Promise<void> {
130-
// Click the dropdown to open it
127+
async selectArticleType(articleType?: string): Promise<void> {
128+
if(articleType == null || articleType === undefined) {
129+
return;
130+
}
131+
131132
if (await this.articleTypeDropdown.isVisible()) {
132133
await this.articleTypeDropdown.click();
133134
}

test/End-To-End/src/admin/NavigationFormPage.ts

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { Page, Locator } from '@playwright/test';
22
import { AdminPageBase } from '@models/AdminPageBase';
33

44
export interface NavigationFormData {
5-
title: string;
6-
url: string;
5+
title?: string;
6+
url?: string;
77
isMobile?: boolean;
88
html?: string;
9-
status?: '有效' | '无效';
9+
status?: string;
1010
description?: string;
1111
}
1212

@@ -63,28 +63,12 @@ export class NavigationFormPage extends AdminPageBase {
6363
* @param formData - Navigation form data to fill
6464
*/
6565
async fillNavigationForm(formData: NavigationFormData): Promise<void> {
66-
await this.titleInput.fill(formData.title);
67-
await this.urlInput.fill(formData.url);
68-
69-
if (formData.isMobile !== undefined) {
70-
if (formData.isMobile) {
71-
await this.isMobileCheckbox.check();
72-
} else {
73-
await this.isMobileCheckbox.uncheck();
74-
}
75-
}
76-
77-
if (formData.html) {
78-
await this.htmlTextarea.fill(formData.html);
79-
}
80-
81-
if (formData.status) {
82-
await this.statusSelect.selectOption(formData.status);
83-
}
84-
85-
if (formData.description) {
86-
await this.descriptionTextarea.fill(formData.description);
87-
}
66+
await this.fill(this.titleInput, formData.title);
67+
await this.fill(this.urlInput, formData.url);
68+
await this.fill(this.isMobileCheckbox, formData.isMobile);
69+
await this.fill(this.htmlTextarea, formData.html);
70+
await this.fill(this.statusSelect, formData.status);
71+
await this.fill(this.descriptionTextarea, formData.description);
8872
}
8973

9074
/**

test/End-To-End/src/admin/PageFormPage.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { Page, expect } from "@playwright/test";
22
import { AdminPageBase } from "@models/AdminPageBase";
33
export interface PageFormData {
4-
id: string;
5-
name: string;
6-
title: string;
7-
path: string;
8-
layout: string;
9-
keywords: string;
10-
description: string;
11-
status: string;
4+
id?: string;
5+
name?: string;
6+
title?: string;
7+
path?: string;
8+
layout?: string;
9+
keywords?: string;
10+
description?: string;
11+
status?: string;
1212
}
1313
export class PageFormPage extends AdminPageBase {
1414
constructor(page: Page) {
@@ -20,27 +20,15 @@ export class PageFormPage extends AdminPageBase {
2020
}
2121

2222
async fillForm(data: PageFormData): Promise<void> {
23-
if (data.name) {
24-
await this.page.fill('input[name="PageName"]', data.name);
25-
}
26-
if (data.title) {
27-
await this.page.fill('input[name="Title"]', data.title);
28-
}
29-
if (data.path) {
30-
await this.page.fill('input[name="PageUrl"]', data.path);
31-
}
23+
await this.fill(this.page.locator('input[name="PageName"]'), data.name);
24+
await this.fill(this.page.locator('input[name="Title"]'), data.title);
25+
await this.fill(this.page.locator('input[name="PageUrl"]'), data.path);
3226
if (data.layout) {
3327
await this.page.locator('.layout-item').filter({ hasText: data.layout }).click();
3428
}
35-
if (data.keywords) {
36-
await this.page.fill('input[name="MetaKeyWorlds"]', data.keywords);
37-
}
38-
if (data.description) {
39-
await this.page.fill('input[name="MetaDescription"]', data.description);
40-
}
41-
if (data.status) {
42-
await this.page.locator('select[name="Status"]').selectOption(data.status);
43-
}
29+
await this.fill(this.page.locator('input[name="MetaKeyWorlds"]'), data.keywords);
30+
await this.fill(this.page.locator('input[name="MetaDescription"]'), data.description);
31+
await this.fill(this.page.locator('select[name="Status"]'), data.status);
4432
}
4533
async submitForm(): Promise<void> {
4634
await this.page.click('input[type="submit"]');

0 commit comments

Comments
 (0)