Skip to content

Commit 9746d96

Browse files
committed
Article
1 parent b5ebef5 commit 9746d96

File tree

5 files changed

+283
-6
lines changed

5 files changed

+283
-6
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Here's what you should do:
2323
* Constructor that accepts a Playwright Page object
2424
* Public methods that represent key actions on the page (clicks, fills, navigations)
2525
* Locator properties for important elements on the page
26-
* Get the page html (use javascript `console.log(document.body.innerHTML)` to get page html) so you can get the best locators:
26+
* Get the page html (use javascript `document.body.innerHTML` to get page html) then you can get the best locators:
2727
- Prioritize data-testid attributes: page.locator('[data-testid="button"]')
2828
- Use semantic selectors: page.getByRole('button', { name: 'Submit' })
2929
- Use text content: page.getByText('Save')
@@ -56,7 +56,7 @@ EXAMPLE STRUCTURE:
5656
For a frontend page:
5757
```
5858
import { Page } from '@playwright/test';
59-
import { PageBase } from './PageBase';
59+
import { PageBase } from '@models/PageBase';
6060
6161
export class HomePage extends PageBase {
6262
@@ -73,7 +73,7 @@ export class HomePage extends PageBase {
7373
For an admin page:
7474
```
7575
import { Page } from '@playwright/test';
76-
import { AdminPageBase } from "../models/AdminPageBase";
76+
import { AdminPageBase } from "@models/AdminPageBase";
7777
7878
export class ArticleManagementPage extends AdminPageBase {
7979
@@ -88,5 +88,5 @@ export class ArticleManagementPage extends AdminPageBase {
8888
}
8989
```
9090
91-
Analyze the URL provided by the user, determine the page type, and create the appropriate page object model file.
91+
Analyze the page provided by the user, determine the page type, and create the appropriate page object model file.
9292
"""

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: 'http://demo.zkea.net',
3030

3131
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
3232
trace: 'on-first-retry',
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { Page, Locator } from '@playwright/test';
2+
import { AdminPageBase } from '@models/AdminPageBase';
3+
4+
export class ArticleFormPage extends AdminPageBase {
5+
// Form fields
6+
readonly titleField: Locator;
7+
readonly urlField: Locator;
8+
readonly summaryField: Locator;
9+
readonly metaKeywordsField: Locator;
10+
readonly metaDescriptionField: Locator;
11+
readonly counterField: Locator;
12+
readonly contentFrame: Locator;
13+
readonly imageThumbUrlField: Locator;
14+
readonly imageUrlField: Locator;
15+
readonly articleTypeField: Locator;
16+
readonly publishDateField: Locator;
17+
readonly statusDropdown: Locator;
18+
readonly descriptionField: Locator;
19+
20+
// Buttons
21+
readonly saveButton: Locator;
22+
readonly saveAndExitButton: Locator;
23+
readonly publishButton: Locator;
24+
readonly cancelButton: Locator;
25+
readonly randomUrlButton: Locator;
26+
27+
// Article type dropdown elements
28+
readonly articleTypeDropdown: Locator;
29+
30+
constructor(page: Page) {
31+
super(page);
32+
// Form fields locators
33+
this.titleField = page.locator('#Title');
34+
this.urlField = page.locator('#Url');
35+
this.summaryField = page.locator('#Summary');
36+
this.metaKeywordsField = page.locator('#MetaKeyWords');
37+
this.metaDescriptionField = page.locator('#MetaDescription');
38+
this.counterField = page.locator('#Counter');
39+
this.contentFrame = page.locator('#ArticleContent');
40+
this.imageThumbUrlField = page.locator('#ImageThumbUrl');
41+
this.imageUrlField = page.locator('#ImageUrl');
42+
this.articleTypeField = page.locator('#ArticleTypeID');
43+
this.publishDateField = page.locator('#PublishDate');
44+
this.statusDropdown = page.locator('#Status');
45+
this.descriptionField = page.locator('#Description');
46+
47+
// Button locators
48+
this.saveButton = page.locator('input[data-value="Create"]');
49+
this.saveAndExitButton = page.locator('input[data-value="CreateAndExit"]');
50+
this.publishButton = page.locator('input[data-value="Publish"]');
51+
this.cancelButton = page.getByRole('link', { name: '取消' });
52+
this.randomUrlButton = page.locator('#Url').locator('..').getByRole('link');
53+
54+
// Article type dropdown elements
55+
this.articleTypeDropdown = page.locator('#dropdown-tree-ArticleTypeID > .form-control');
56+
}
57+
58+
/**
59+
* Navigates to the default page for this page model
60+
*/
61+
async navigateTo(): Promise<void> {
62+
await this.page.goto('/admin/article/create');
63+
// should wait tinymce to load
64+
await this.page.frameLocator('#ArticleContent_ifr').locator('#tinymce').waitFor();
65+
await new Promise(resolve => setTimeout(resolve, 1000));
66+
}
67+
68+
/**
69+
* Navigates to the article creation page
70+
*/
71+
async navigateToCreate(): Promise<void> {
72+
await this.page.goto('/admin/article/create');
73+
}
74+
75+
/**
76+
* Navigates to the edit page for a specific article
77+
* @param articleId The ID of the article to edit
78+
*/
79+
async navigateToEdit(articleId: string): Promise<void> {
80+
await this.page.goto(`/admin/article/edit/${articleId}`);
81+
}
82+
83+
/**
84+
* Fills the article form with provided data
85+
* @param articleData The data to fill in the form
86+
*/
87+
async fillArticleForm(articleData: {
88+
title?: string,
89+
url?: string,
90+
summary?: string,
91+
metaKeywords?: string,
92+
metaDescription?: string,
93+
counter?: string,
94+
content?: string,
95+
imageThumbUrl?: string,
96+
imageUrl?: string,
97+
articleType?: string,
98+
publishDate?: string,
99+
status?: string,
100+
description?: string
101+
}): 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);
113+
114+
// Select article type if provided
115+
if (articleData.articleType) {
116+
await this.selectArticleType(articleData.articleType);
117+
}
118+
119+
// Select status if provided
120+
if (articleData.status) {
121+
await this.statusDropdown.selectOption(articleData.status);
122+
}
123+
}
124+
125+
/**
126+
* Selects an article type from the dropdown tree
127+
* @param articleType The article type to select
128+
*/
129+
async selectArticleType(articleType: string): Promise<void> {
130+
// Click the dropdown to open it
131+
if (await this.articleTypeDropdown.isVisible()) {
132+
await this.articleTypeDropdown.click();
133+
}
134+
135+
const articleTypeOption = this.page.getByRole('treeitem', { name: articleType, exact: true });
136+
await articleTypeOption.click();
137+
}
138+
139+
/**
140+
* Generates a random URL
141+
*/
142+
async generateRandomUrl(): Promise<void> {
143+
await this.randomUrlButton.click();
144+
}
145+
146+
/**
147+
* Saves the article form
148+
*/
149+
async saveArticle(): Promise<void> {
150+
await this.saveButton.click();
151+
}
152+
153+
/**
154+
* Saves the article and exits the form
155+
*/
156+
async saveAndExit(): Promise<void> {
157+
await this.saveAndExitButton.click();
158+
}
159+
160+
/**
161+
* Publishes the article
162+
*/
163+
async publishArticle(): Promise<void> {
164+
await this.publishButton.click();
165+
}
166+
167+
/**
168+
* Cancels the form and returns to the previous page
169+
*/
170+
async cancel(): Promise<void> {
171+
await this.cancelButton.click();
172+
}
173+
}

test/End-To-End/src/models/AdminPageBase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export abstract class AdminPageBase extends PageBase {
2020
}
2121

2222
async logout(): Promise<void> {
23-
await this.page.goto('http://demo.zkea.net/account/logout');
23+
await this.page.goto('/account/logout');
2424
await this.page.waitForURL('/index');
2525
}
2626
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { test, expect } from '@playwright/test';
2+
import { ArticleFormPage } from '../../../src/admin/ArticleFormPage';
3+
4+
test.describe('Article Form Page - Basic Functionality Tests', () => {
5+
let articleFormPage: ArticleFormPage;
6+
7+
test.beforeEach(async ({ page }) => {
8+
// Initialize the page object
9+
articleFormPage = new ArticleFormPage(page);
10+
11+
// Login before each test
12+
await articleFormPage.login();
13+
await articleFormPage.navigateTo();
14+
});
15+
16+
test('should be able to create an article with required fields', async ({ page }) => {
17+
18+
// Fill in the required fields
19+
const timestamp = new Date().getTime();
20+
await articleFormPage.fillArticleForm({
21+
title: 'Test Article ' + timestamp,
22+
url: 'test-article-' + timestamp,
23+
summary: 'This is a test article summary',
24+
content: 'This is the content of the test article.',
25+
articleType: '公司新闻' // News category
26+
});
27+
28+
// Save the article
29+
await articleFormPage.saveArticle();
30+
31+
await expect(page).toHaveURL(/\/admin\/article\/edit\/\d+/);
32+
});
33+
34+
test('should generate a random URL when the random button is clicked', async ({ page }) => {
35+
36+
// Initially the URL field should be empty
37+
await expect(articleFormPage.urlField).toBeEmpty();
38+
39+
// Click the random URL button
40+
await articleFormPage.generateRandomUrl();
41+
42+
// Check that the field now contains a value
43+
await expect(articleFormPage.urlField).not.toBeEmpty();
44+
});
45+
46+
test('should be able to select an article type from the dropdown', async ({ page }) => {
47+
// Select an article type
48+
await articleFormPage.selectArticleType('公司新闻');
49+
50+
// Check that the value has been set correctly
51+
const selectedArticleType = await articleFormPage.articleTypeField.inputValue();
52+
expect(selectedArticleType).not.toBe('');
53+
});
54+
55+
test('should be able to save and exit from the article form', async ({ page }) => {
56+
// Fill in the required fields
57+
const timestamp = new Date().getTime();
58+
await articleFormPage.fillArticleForm({
59+
title: 'Test Article ' + timestamp,
60+
url: 'test-article-' + timestamp,
61+
summary: 'This is a test article summary',
62+
content: 'This is the content of the test article.',
63+
articleType: '公司新闻' // News category
64+
});
65+
66+
// Save and exit
67+
await articleFormPage.saveAndExit();
68+
69+
// After saving and exiting, we should be redirected to the article list page
70+
await expect(page).toHaveURL(/\/admin\/article/);
71+
});
72+
73+
test('should cancel and return to the article list page', async ({ page }) => {
74+
75+
// Click cancel
76+
await articleFormPage.cancel();
77+
78+
// Should be redirected to the article list page
79+
await expect(page).toHaveURL(/\/admin\/article/);
80+
});
81+
82+
83+
test('should be able to publish article', async ({ page }) => {
84+
// Fill in the required fields
85+
const timestamp = new Date().getTime();
86+
await articleFormPage.fillArticleForm({
87+
title: 'Test Article ' + timestamp,
88+
url: 'test-article-' + timestamp,
89+
summary: 'This is a test article summary',
90+
content: 'This is the content of the test article.',
91+
articleType: '公司新闻' // News category
92+
});
93+
94+
// Save and exit
95+
await articleFormPage.saveArticle();
96+
page.on('dialog', async dialog => {
97+
await dialog.accept();
98+
});
99+
await articleFormPage.publishArticle();
100+
// After saving and exiting, we should be redirected to the article list page
101+
await expect(articleFormPage.publishDateField).not.toBeEmpty();
102+
await expect(page.getByText('已发布')).toBeVisible();
103+
});
104+
});

0 commit comments

Comments
 (0)