Add data testing attributes and adapt tests for team account invitations

This commit adds data testing attributes to key elements in the team account invitations features. It also modifies e2e tests to make use of these attributes. Additionally, it introduces minor tweaks to other parts of the system to better facilitate testing, such as adjustments in timeouts and update of some log messages.
This commit is contained in:
giancarlo
2024-04-13 15:27:21 +08:00
parent 24e5c0debd
commit b6b9a9462f
15 changed files with 219 additions and 62 deletions

View File

@@ -17,7 +17,7 @@ export default defineConfig({
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 3 : 1,
/* Limit parallel tests on CI. */
workers: process.env.CI ? 2 : undefined,
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
@@ -32,6 +32,13 @@ export default defineConfig({
trace: 'on-first-retry',
},
// test timeout set to 2 minutes
timeout: 2 * 60 * 1000,
expect: {
// expect timeout set to 10 seconds
timeout: 10 * 1000
},
/* Configure projects for major browsers */
projects: [
{

View File

@@ -27,7 +27,7 @@ export class AuthPageObject {
email: string,
password: string
}) {
await this.page.waitForTimeout(100);
await this.page.waitForTimeout(1000);
await this.page.fill('input[name="email"]', params.email);
await this.page.fill('input[name="password"]', params.password);
@@ -39,7 +39,7 @@ export class AuthPageObject {
password: string,
repeatPassword: string
}) {
await this.page.waitForTimeout(100);
await this.page.waitForTimeout(1000);
await this.page.fill('input[name="email"]', params.email);
await this.page.fill('input[name="password"]', params.password);
@@ -47,7 +47,9 @@ export class AuthPageObject {
await this.page.click('button[type="submit"]');
}
async visitConfirmEmailLink(email: string) {
async visitConfirmEmailLink(email: string, params?: {
deleteAfter: boolean
}) {
return expect(async() => {
const res = await this.mailbox.visitMailbox(email);

View File

@@ -1,4 +1,4 @@
import { Page } from '@playwright/test';
import { expect, Page } from '@playwright/test';
import { AuthPageObject } from '../authentication/auth.po';
import { TeamAccountsPageObject } from '../team-accounts/team-accounts.po';
@@ -44,7 +44,7 @@ export class InvitationsPageObject {
await form.locator('button[type="submit"]').click();
}
public navigateToMembers() {
navigateToMembers() {
return this.page.locator('a', {
hasText: 'Members',
}).click();
@@ -54,7 +54,47 @@ export class InvitationsPageObject {
await this.page.locator('[data-test="invite-members-form-trigger"]').click();
}
async getInvitations() {
return this.page.locator('[data-test="invitation-email"]');
}
private getInviteForm() {
return this.page.locator('[data-test="invite-members-form"]');
}
async deleteInvitation(email: string) {
const actions = this.getInvitationRow(email).getByRole('button');
await actions.click();
await this.page.locator('[data-test="remove-invitation-trigger"]').click();
await this.page.click('[data-test="delete-invitation-form"] button[type="submit"]');
}
getInvitationRow(email: string) {
return this.page.getByRole('row', { name: email });
}
async updateInvitation(email: string, role: string) {
const row = this.getInvitationRow(email);
const actions = row.getByRole('button');
await actions.click();
await this.page.locator('[data-test="update-invitation-trigger"]').click();
await this.page.click(`[data-test="role-selector-trigger"]`);
await this.page.click(`[data-test="role-option-${role}"]`);
await this.page.click('[data-test="update-invitation-form"] button[type="submit"]');
}
async acceptInvitation() {
await this.page.locator('[data-test="join-team-form"] button[type="submit"]').click();
await this.page.waitForResponse(response => {
return response.url().includes('/join') && response.request().method() === 'POST';
})
}
}

View File

@@ -1,4 +1,4 @@
import { Page, test } from '@playwright/test';
import { expect, Page, test } from '@playwright/test';
import { InvitationsPageObject } from './invitations.po';
test.describe('Invitations', () => {
@@ -12,7 +12,7 @@ test.describe('Invitations', () => {
await invitations.setup();
});
test('user invite users', async ({page}) => {
test('Full invite flow', async ({page}) => {
await page.waitForLoadState('networkidle');
await invitations.navigateToMembers();
@@ -30,5 +30,80 @@ test.describe('Invitations', () => {
];
await invitations.inviteMembers(invites);
const firstEmail = invites[0]!.email;
await expect(await invitations.getInvitations()).toHaveCount(2)
// sign out and sign in with the first email
await invitations.auth.signOut();
await invitations.auth.visitConfirmEmailLink(invites[0]!.email, {
deleteAfter: true
});
await invitations.auth.signUp({
email: firstEmail,
password: 'password',
repeatPassword: 'password'
});
await invitations.auth.visitConfirmEmailLink(firstEmail);
await invitations.acceptInvitation();
await invitations.teamAccounts.openAccountsSelector();
await expect(await invitations.teamAccounts.getTeams()).toHaveCount(1);
});
test('users can delete invites', async ({page}) => {
await page.waitForLoadState('networkidle');
await invitations.navigateToMembers();
await invitations.openInviteForm();
const email = invitations.auth.createRandomEmail();
const invites = [
{
email,
role: 'member'
},
];
await invitations.inviteMembers(invites);
await expect(await invitations.getInvitations()).toHaveCount(1);
await invitations.deleteInvitation(email);
await expect(await invitations.getInvitations()).toHaveCount(0);
});
test('users can update invites', async ({page}) => {
await page.waitForLoadState('networkidle');
await invitations.navigateToMembers();
await invitations.openInviteForm();
const email = invitations.auth.createRandomEmail();
const invites = [
{
email,
role: 'member'
},
];
await invitations.inviteMembers(invites);
await expect(await invitations.getInvitations()).toHaveCount(1);
await invitations.updateInvitation(email, 'owner');
const row = invitations.getInvitationRow(email);
await expect(row.locator('[data-test="member-role-badge"]')).toHaveText('owner');
});
});

View File

@@ -18,7 +18,17 @@ export class TeamAccountsPageObject {
async getTeamFromSelector(teamSlug: string) {
await this.openAccountsSelector();
return this.page.locator(`[data-test="account-selector-team-${teamSlug}"]`);
return this.page.locator(`[data-test="account-selector-team"][data-value="${teamSlug}"]`);
}
async selectAccount(teamName: string) {
await this.page.click(`[data-test="account-selector-team"][data-name="${teamName}"]`);
}
async getTeams() {
await this.openAccountsSelector();
return this.page.locator('[data-test="account-selector-team"]');
}
async goToSettings() {

View File

@@ -7,7 +7,9 @@ export class Mailbox {
) {
}
async visitMailbox(email: string) {
async visitMailbox(email: string, params?: {
deleteAfter: boolean
}) {
const mailbox = email.split('@')[0];
console.log(`Visiting mailbox ${mailbox} ...`)
@@ -16,7 +18,7 @@ export class Mailbox {
throw new Error('Invalid email');
}
const json = await this.getInviteEmail(mailbox);
const json = await this.getInviteEmail(mailbox, params);
if (!json.body) {
throw new Error('Email body was not found');