Skip to content

Commit 5fb3e19

Browse files
authored
[Playwright] Follow-up survey tests (#2174)
* playwright test for follow-up survey
1 parent bfab85f commit 5fb3e19

40 files changed

+448
-70
lines changed

playwright-e2e/dsm/component/filters/sections/search/search.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export class Search {
182182

183183
/* XPaths */
184184

185-
private get openButtonXPath(): string {
185+
public get openButtonXPath(): string {
186186
return (
187187
"//div[text()[normalize-space()='Search'] and button[.//*[local-name()='svg' and @data-icon='search']/*[local-name()='path']]]/button"
188188
);

playwright-e2e/dsm/component/modal.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Locator, Page } from '@playwright/test';
22
import Button from 'dss/component/button';
33
import Input from 'dss/component/input';
44
import Radiobutton from 'dss/component/radiobutton';
5+
import { waitForNoSpinner } from 'utils/test-utils';
56

67
export default class Modal {
78
private readonly rootSelector: Locator;
@@ -31,6 +32,15 @@ export default class Modal {
3132
return this.headerLocator().innerText();
3233
}
3334

35+
async getBodyText(): Promise<string> {
36+
return this.bodyLocator().innerText();
37+
}
38+
39+
async close(): Promise<void> {
40+
await this.getButton({ label: 'Close' }).click();
41+
await waitForNoSpinner(this.page);
42+
}
43+
3444
public getButton(opts: { label?: string | RegExp; ddpTestID?: string }): Button {
3545
const { label, ddpTestID } = opts;
3646
return new Button(this.page, { label, ddpTestID, root: this.toLocator() });

playwright-e2e/dsm/component/tables/participant-list-table.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import ParticipantPage from 'dsm/pages/participant-page/participant-page';
44
import {ParticipantsListPaginator} from 'lib/component/dsm/paginators/participantsListPaginator';
55
import {rows} from 'lib/component/dsm/paginators/types/rowsPerPage';
66
import { getDate, offsetDaysFromToday } from 'utils/date-utils';
7-
import { AdditionalFilter } from '../filters/sections/search/search-enums';
7+
import { waitForNoSpinner } from 'utils/test-utils';
8+
import { AdditionalFilter } from 'dsm/component/filters/sections/search/search-enums';
89
import ParticipantListPage from 'dsm/pages/participant-list-page';
9-
import addColumnsToParticipantList from 'dsm/pages/participant-list-page';
1010

1111
export class ParticipantListTable extends Table {
1212
private readonly _participantPage: ParticipantPage = new ParticipantPage(this.page);
@@ -34,6 +34,7 @@ export class ParticipantListTable extends Table {
3434

3535
public async openParticipantPageAt(position: number): Promise<ParticipantPage> {
3636
await this.getParticipantAt(position).click();
37+
await waitForNoSpinner(this.page);
3738
await this._participantPage.assertPageTitle();
3839
return this._participantPage;
3940
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Page } from '@playwright/test';
2+
import Table from 'dss/component/table';
3+
4+
export default class PreviousSurveysTable extends Table {
5+
constructor(page: Page) {
6+
super(page, { cssClassAttribute: '.table' });
7+
}
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Page } from '@playwright/test';
2+
import { waitForNoSpinner } from 'utils/test-utils';
3+
4+
export default abstract class DsmPageBase {
5+
protected readonly page: Page;
6+
protected readonly baseUrl: string | undefined;
7+
8+
protected constructor(page: Page, baseURL?: string) {
9+
this.page = page;
10+
this.baseUrl = baseURL;
11+
}
12+
13+
async reload(): Promise<void> {
14+
await this.page.reload();
15+
await waitForNoSpinner(this.page);
16+
}
17+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { APIRequestContext, expect, Page } from '@playwright/test';
2+
import { MiscellaneousEnum } from 'dsm/component/navigation/enums/miscellaneousNav-enum';
3+
import { Navigation } from 'dsm/component/navigation/navigation';
4+
import PreviousSurveysTable from 'dsm/component/tables/previous-surveys-table';
5+
import { WelcomePage } from 'dsm/pages/welcome-page';
6+
import Button from 'dss/component/button';
7+
import Input from 'dss/component/input';
8+
import Select from 'dss/component/select';
9+
import { waitForNoSpinner } from 'utils/test-utils';
10+
11+
export default class FollowUpSurveyPage {
12+
private readonly _table: PreviousSurveysTable = new PreviousSurveysTable(this.page);
13+
14+
static async goto(page: Page, study: string, request: APIRequestContext): Promise<FollowUpSurveyPage> {
15+
const welcomePage = new WelcomePage(page);
16+
await welcomePage.selectStudy(study);
17+
18+
const navigation = new Navigation(page, request);
19+
await navigation.selectMiscellaneous(MiscellaneousEnum.FOLLOW_UP_SURVEY);
20+
const followUpPage = new FollowUpSurveyPage(page);
21+
await followUpPage.waitForReady();
22+
return followUpPage;
23+
}
24+
25+
constructor(private readonly page: Page) {}
26+
27+
public get previousSurveysTable(): PreviousSurveysTable {
28+
return this._table;
29+
}
30+
31+
public async waitForReady(): Promise<void> {
32+
await expect(this.page.locator('h1')).toHaveText(/^\s*Follow-Up Survey\s*$/);
33+
await expect(this.select().toLocator()).toBeVisible({ timeout: 30000 });
34+
await waitForNoSpinner(this.page);
35+
}
36+
37+
public async selectSurvey(survey: string): Promise<void> {
38+
await this.select().selectOption(survey);
39+
await waitForNoSpinner(this.page);
40+
}
41+
42+
public async participantId(id: string): Promise<void> {
43+
const input = new Input(this.page, { label: 'Participant ID' });
44+
await input.fill(id);
45+
}
46+
47+
public async reasonForFollowUpSurvey(reason: string): Promise<void> {
48+
const input = new Input(this.page, { label: 'Reason for Follow-Up Survey' });
49+
await input.fill(reason);
50+
}
51+
52+
public async createSurvey(): Promise<void> {
53+
const button = new Button(this.page, { label: 'Create Survey', root: 'app-survey' });
54+
await button.click();
55+
await waitForNoSpinner(this.page);
56+
}
57+
58+
public select(): Select {
59+
return new Select(this.page, { label: 'Survey', root: 'app-survey' });
60+
}
61+
62+
public async reloadTable(): Promise<void> {
63+
const button = new Button(this.page, { label: 'Reload', root: 'app-survey' });
64+
await button.click();
65+
await waitForNoSpinner(this.page);
66+
}
67+
}

playwright-e2e/dsm/pages/mailing-list-page.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Download, expect, Locator, Page } from '@playwright/test';
2+
import DsmPageBase from 'dsm/pages/dsm-page-base';
23
import Table from 'dss/component/table';
34

45
export const COLUMN = {
@@ -8,11 +9,12 @@ export const COLUMN = {
89
LAST_NAME: 'Last Name'
910
}
1011

11-
export default class MailingListPage {
12+
export default class MailingListPage extends DsmPageBase {
1213
private readonly title: string | RegExp;
1314
readonly downloadButton: Locator;
1415

15-
constructor(private readonly page: Page, study: string|RegExp) {
16+
constructor(page: Page, study: string|RegExp) {
17+
super(page);
1618
this.title = study;
1719
this.downloadButton = this.page.getByRole('button', { name: 'Download mailing list' })
1820
}

playwright-e2e/dsm/pages/participant-list-page.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export default class ParticipantListPage {
2121
const navigation = new Navigation(page, request);
2222
const participantListPage = await navigation.selectFromStudy<ParticipantListPage>(StudyNavEnum.PARTICIPANT_LIST);
2323
await participantListPage.waitForReady();
24-
await participantListPage.assertPageTitle();
2524

2625
const participantsTable = participantListPage.participantListTable;
2726
const rowsTotal = await participantsTable.rowLocator().count();
@@ -32,7 +31,9 @@ export default class ParticipantListPage {
3231
constructor(private readonly page: Page) {}
3332

3433
public async waitForReady(): Promise<void> {
34+
await this.assertPageTitle();
3535
await waitForNoSpinner(this.page);
36+
await expect(this.page.locator(this.filters.searchPanel.openButtonXPath)).toBeVisible();
3637
}
3738

3839
public async selectAll(): Promise<void> {
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Page } from '@playwright/test';
1+
import { expect, Page } from '@playwright/test';
22
import Select from 'dss/component/select';
3+
import { waitForNoSpinner } from 'utils/test-utils';
34

45
export class WelcomePage {
56
private readonly selectWidget: Select = new Select(this.page, { label: 'Select study' });
@@ -8,5 +9,8 @@ export class WelcomePage {
89

910
public async selectStudy(studyName: string): Promise<void> {
1011
await this.selectWidget.selectOption(studyName);
12+
await waitForNoSpinner(this.page);
13+
await expect(this.page.locator('h1')).toHaveText('Welcome to the DDP Study Management System');
14+
await expect(this.page.locator('h2')).toHaveText(`You have selected the ${studyName} study.`);
1115
}
1216
}

playwright-e2e/dss/component/select.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,32 @@ export default class Select extends WidgetBase {
7171
break;
7272
}
7373
}
74+
75+
/**
76+
* Finds all options in a Select or mat-select
77+
* @returns {Promise<string[]>}
78+
*/
79+
async getAllOptions(): Promise<string[]> {
80+
let options;
81+
const tagName = await this.toLocator().evaluate((elem) => elem.tagName);
82+
switch (tagName) {
83+
case 'SELECT':
84+
options = await this.toLocator().locator('option').allInnerTexts();
85+
break;
86+
default:
87+
// Click first to open mat-select dropdown
88+
await this.toLocator().click();
89+
const ariaControlsId = await this.toLocator().getAttribute('aria-controls');
90+
if (!ariaControlsId) {
91+
throw Error('ERROR: Cannot find attribute "aria-controls"');
92+
}
93+
const dropdown = this.page.locator(`#${ariaControlsId}[role="listbox"]`);
94+
options = await dropdown.locator('mat-option .mat-option-text').allInnerTexts();
95+
break;
96+
}
97+
if (!options) {
98+
throw new Error(`Failed to find all options in Select or mat-select`);
99+
}
100+
return options!;
101+
}
74102
}

0 commit comments

Comments
 (0)