Skip to content

Commit c6760f2

Browse files
committed
Advancing in page objects
1 parent 4a8fcf4 commit c6760f2

19 files changed

+530
-143
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,5 @@ dist
112112
/playwright-report/
113113
/blob-report/
114114
/playwright/.cache/
115+
/*-snapshots/
116+
*.png

.vscode/extensions.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"recommendations": [
3+
"cs50.vscode-presentation-mode"
4+
]
5+
}

infra/po/pages.ts

Lines changed: 164 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,137 @@
11
import { Locator, Page } from "@playwright/test";
22

3-
export abstract class AbstractPage {
4-
protected page: Page;
3+
export abstract class BaseComponent {
4+
constructor(protected page: Page, protected rootLocator?: Locator) {
5+
6+
}
7+
8+
protected searcher() : Page | Locator {
9+
if (this.rootLocator) {
10+
return this.rootLocator;
11+
} else {
12+
return this.page;
13+
}
14+
}
15+
}
16+
17+
export abstract class BasePage extends BaseComponent{
18+
constructor(page: Page) {
19+
super(page);
20+
}
21+
}
22+
23+
export class MainMenuComponent extends BaseComponent {
24+
private readonly workPackages: Locator;
525

626
constructor(page: Page) {
7-
this.page = page;
27+
super(page);
28+
this.workPackages = page.locator("#main-menu-work-packages");
829
}
930

31+
async clickOnWorkPackagesItm(): Promise<AllOpenWpPage> {
32+
await this.workPackages.click();
33+
return new AllOpenWpPage(this.page);
34+
}
1035
}
1136

12-
export class OverviewPage extends AbstractPage {
37+
export class OverviewPage extends BasePage {
38+
public readonly mainMenu: MainMenuComponent;
39+
40+
constructor(page: Page) {
41+
super(page);
42+
this.mainMenu = new MainMenuComponent(page);
43+
}
44+
45+
}
46+
47+
export class NewTaskPage extends BasePage {
48+
private readonly subjectTb: Locator;
49+
private readonly estimatedTimeTb: Locator;
50+
private readonly saveBtn: Locator;
51+
52+
constructor(page: Page) {
53+
super(page);
54+
this.subjectTb = page.getByLabel('Subject', { exact: true });
55+
this.estimatedTimeTb = page.getByLabel('Estimated time', { exact: true })
56+
this.saveBtn = page.getByRole('button', { name: 'Save' });
57+
}
58+
59+
async fillSubject(subject: string): Promise<NewTaskPage> {
60+
await this.subjectTb.fill(subject);
61+
return this;
62+
}
63+
64+
async fillEstimatedTime(estimatedTime: string): Promise<NewTaskPage> {
65+
// await page.locator('#wp-new-inline-edit--field-assignee').getByRole('combobox').click();
66+
await this.estimatedTimeTb.fill(estimatedTime);
67+
return this;
68+
}
69+
70+
async clickOnSaveBtn(): Promise<OverviewPage> {
71+
await this.saveBtn.click();
72+
return new OverviewPage(this.page);
73+
}
1374

1475
}
1576

16-
export class OpenProjectPage extends AbstractPage {
77+
78+
export class FilterComponent extends BaseComponent {
79+
80+
private readonly filterByTextTb: Locator;
81+
82+
constructor(page: Page) {
83+
super(page);
84+
this.filterByTextTb = page.getByPlaceholder('Subject, description, comments, ...');
85+
}
86+
87+
async fillFilterByText(filterText: string): Promise<FilterComponent> {
88+
const responsePromise = this.page.waitForResponse('**/queries/**');
89+
await this.filterByTextTb.fill(filterText);
90+
const response = await responsePromise
91+
return this;
92+
}
93+
94+
}
95+
96+
export class Table extends BaseComponent {
97+
constructor(page: Page, rootLocator: Locator) {
98+
super(page, rootLocator);
99+
}
100+
101+
async containsText(text: string): Promise<boolean> {
102+
return await this.searcher().locator('text=' + text).first().isVisible();
103+
}
104+
}
105+
106+
export class ApplicationMenu extends BaseComponent {
107+
108+
private readonly signOutBtn: Locator;
109+
110+
constructor(page: Page, rootLocator: Locator) {
111+
super(page, rootLocator);
112+
this.signOutBtn = this.searcher().getByRole('link', { name: 'Sign out' });
113+
}
114+
115+
async clickOnSignOutBtn(): Promise<LoginPage> {
116+
await this.signOutBtn.click();
117+
return new LoginPage(this.page);
118+
}
119+
}
120+
121+
122+
export class OpenProjectPage extends BasePage {
17123

18124
private readonly selectAProjectToggle: Locator;
125+
private readonly applicationMenuLnk: Locator;
126+
private readonly applicationMenuRoot: Locator;
127+
128+
19129

20130
constructor(page: Page) {
21131
super(page);
22132
this.selectAProjectToggle = page.locator("#projects-menu i");
133+
this.applicationMenuLnk = page.locator("a[title='OpenProject Admin']").locator(".op-app-menu--item-title");
134+
this.applicationMenuRoot = page.locator("#user-menu");
23135
}
24136

25137
async clickOnSelectAProjectToggle(): Promise<OpenProjectPage> {
@@ -28,10 +140,15 @@ export class OpenProjectPage extends AbstractPage {
28140
}
29141

30142
async clickOnProjectName(projectName: string): Promise<OverviewPage> {
31-
await this.page.locator(`#ui-id-5 >> text=${projectName}`).click()
143+
await this.page.locator('.project-search-results').getByRole('link', { name: projectName }).click();
32144
return new OverviewPage(this.page);
33145
}
34146

147+
async clickOnUserMenu(): Promise<ApplicationMenu> {
148+
await this.applicationMenuLnk.click();
149+
return new ApplicationMenu(this.page, this.applicationMenuRoot);
150+
}
151+
35152
async doSelectProject(projectName: string): Promise<OverviewPage> {
36153
await this.clickOnSelectAProjectToggle();
37154
await this.clickOnProjectName(projectName);
@@ -40,7 +157,47 @@ export class OpenProjectPage extends AbstractPage {
40157

41158
}
42159

43-
export class LoginPage extends AbstractPage {
160+
161+
export class AllOpenWpPage extends OpenProjectPage {
162+
163+
private readonly createNewWpBtn: Locator;
164+
private readonly taskBtn: Locator;
165+
private readonly activateFilterBtn: Locator;
166+
private readonly deactivateFilterBtn: Locator;
167+
public readonly table: Table;
168+
169+
170+
constructor(page: Page) {
171+
super(page);
172+
this.createNewWpBtn = page.locator('wp-create-button').getByLabel('Create new work package');
173+
this.taskBtn = page.locator('#types-context-menu').getByLabel('Task' ,{ exact: true });
174+
this.activateFilterBtn = page.getByRole('button', { name: 'Activate Filter' });
175+
this.deactivateFilterBtn = page.getByRole('button', { name: 'Deactivate Filter' });
176+
this.table = new Table(page, page.locator('.work-package-table'));
177+
}
178+
179+
async clickOnCreateNewWorkPackageBtn(): Promise<NewTaskPage> {
180+
await this.createNewWpBtn.click();
181+
await this.taskBtn.click();
182+
return new NewTaskPage(this.page);
183+
}
184+
185+
async clickOnActivateFilterBtn(): Promise<FilterComponent> {
186+
await this.activateFilterBtn.click();
187+
return new FilterComponent(this.page);
188+
}
189+
190+
async clickOnDeactivateFilterBtn(): Promise<AllOpenWpPage> {
191+
await this.deactivateFilterBtn.click();
192+
return new AllOpenWpPage(this.page);
193+
}
194+
195+
196+
}
197+
198+
199+
200+
export class LoginPage extends BasePage {
44201
/**
45202
* @param {import('playwright').Page} page
46203
*/

playwright.config.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ export default defineConfig({
1616
/* Fail the build on CI if you accidentally left test.only in the source code. */
1717
forbidOnly: !!process.env.CI,
1818
/* Retry on CI only */
19-
retries: process.env.CI ? 2 : 0,
19+
retries: 1,
2020
/* Opt out of parallel tests on CI. */
21-
workers: process.env.CI ? 1 : undefined,
21+
workers: 4,
22+
// workers: process.env.CI ? 1 : undefined,
2223
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
2324
reporter: 'html',
2425
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
@@ -28,7 +29,7 @@ export default defineConfig({
2829
// baseURL: 'http://127.0.0.1:3000',
2930

3031
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
31-
trace: 'on-first-retry',
32+
trace: 'on',
3233
},
3334

3435
/* Configure projects for major browsers */
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe.configure({ mode: 'parallel' });
4+
5+
// Workers #Tests Duration - All Tests Duration - Single Test
6+
// 1 8 29 3.625
7+
// 4 8 23 2.875
8+
// 8 8 32 4
9+
10+
11+
for (const counter of ['0', '1', '2', '3', '4', '5', '6', '7']) {
12+
test(`testing with ${counter}`, async ({ page }) => {
13+
await page.goto('https://demo.applitools.com/');
14+
await page.getByPlaceholder('Enter your username').click();
15+
await page.getByPlaceholder('Enter your username').fill('user');
16+
await page.getByPlaceholder('Enter your password').click();
17+
await page.getByPlaceholder('Enter your password').fill('password');
18+
await page.getByRole('link', { name: 'Sign in' }).click();
19+
await page.locator('.os-icon').first().click();
20+
await page.getByRole('link', { name: ' Make Payment' }).click();
21+
await page.getByRole('link', { name: ' Add Account' }).click();
22+
await page.locator('.avatar-w > img').first().click();
23+
await page.locator('.menu-w > .logged-user-w > .logged-user-i > .avatar-w > img').click();
24+
await page.locator('.logged-user-toggler-arrow > .os-icon').click();
25+
await page.getByRole('link', { name: '  Credit cards' }).click();
26+
await page.getByRole('link', { name: '  Debit cards' }).click();
27+
await page.getByRole('link', { name: '  Loans' }).click();
28+
await page.getByRole('link', { name: '  Mortgages' }).click();
29+
await page.getByRole('link', { name: 'View Statement ' }).click();
30+
await expect(page.locator('body')).toContainText('$350');
31+
await page.locator('.logged-user-w').first().click();
32+
});
33+
34+
}
35+

0 commit comments

Comments
 (0)