From ca98e625c20af9b4ed1719d8a3a951bf9f99959a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20B=C3=A9gaudeau?= Date: Wed, 6 Sep 2023 17:07:12 +0200 Subject: [PATCH] [test] Improve our integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Bégaudeau --- .../svalyn-studio-app/playwright.config.ts | 2 +- .../tests/e2e/admin/manage-accounts.spec.ts | 30 ++++++++++ .../authentication/authenticate.spec.ts} | 11 ++-- .../organization/create-organization.spec.ts} | 4 +- .../update-organization-members.spec.ts | 44 ++++++++++++++ .../organization/update-organization.spec.ts} | 12 ++-- .../project/create-project.spec.ts} | 21 +++---- frontend/svalyn-studio-app/tests/fixture.ts | 44 +++++++++++++- .../svalyn-studio-app/tests/fixture.types.ts | 21 ++++++- .../svalyn-studio-app/tests/pages/HomePage.ts | 28 +++++++++ .../tests/pages/admin/AdminAccountsPage.ts | 28 +++++++++ .../tests/pages/admin/AdminNewAccountPage.ts | 28 +++++++++ .../OrganizationMembersPage.ts} | 4 +- .../pages/organization/OrganizationPage.ts | 49 +++++++++++++++ .../OrganizationSettingsPage.ts | 8 ++- .../tests/pages/profile/InvitationsPage.ts | 28 +++++++++ .../tests/pages/profile/ProfilePage.ts | 29 +++++++++ .../tests/pages/profile/SettingsPage.ts | 28 +++++++++ .../tests/widgets/UserMenu.ts | 60 +++++++++++++++++++ 19 files changed, 443 insertions(+), 36 deletions(-) create mode 100644 frontend/svalyn-studio-app/tests/e2e/admin/manage-accounts.spec.ts rename frontend/svalyn-studio-app/tests/{login/login.spec.ts => e2e/authentication/authenticate.spec.ts} (91%) rename frontend/svalyn-studio-app/tests/{organization/new-organization.spec.ts => e2e/organization/create-organization.spec.ts} (96%) create mode 100644 frontend/svalyn-studio-app/tests/e2e/organization/update-organization-members.spec.ts rename frontend/svalyn-studio-app/tests/{organization/organization.spec.ts => e2e/organization/update-organization.spec.ts} (82%) rename frontend/svalyn-studio-app/tests/{project/new-project.spec.ts => e2e/project/create-project.spec.ts} (68%) create mode 100644 frontend/svalyn-studio-app/tests/pages/HomePage.ts create mode 100644 frontend/svalyn-studio-app/tests/pages/admin/AdminAccountsPage.ts create mode 100644 frontend/svalyn-studio-app/tests/pages/admin/AdminNewAccountPage.ts rename frontend/svalyn-studio-app/tests/pages/{OrganizationPage.ts => organization/OrganizationMembersPage.ts} (95%) create mode 100644 frontend/svalyn-studio-app/tests/pages/organization/OrganizationPage.ts rename frontend/svalyn-studio-app/tests/pages/{ => organization}/OrganizationSettingsPage.ts (85%) create mode 100644 frontend/svalyn-studio-app/tests/pages/profile/InvitationsPage.ts create mode 100644 frontend/svalyn-studio-app/tests/pages/profile/ProfilePage.ts create mode 100644 frontend/svalyn-studio-app/tests/pages/profile/SettingsPage.ts create mode 100644 frontend/svalyn-studio-app/tests/widgets/UserMenu.ts diff --git a/frontend/svalyn-studio-app/playwright.config.ts b/frontend/svalyn-studio-app/playwright.config.ts index 57859e6b..d43158cb 100644 --- a/frontend/svalyn-studio-app/playwright.config.ts +++ b/frontend/svalyn-studio-app/playwright.config.ts @@ -19,7 +19,7 @@ const config: PlaywrightTestConfig = { * Maximum time expect() should wait for the condition to be met. * For example in `await expect(locator).toHaveText();` */ - timeout: 5000 + timeout: 20 * 1000, }, /* Run tests in files in parallel */ fullyParallel: true, diff --git a/frontend/svalyn-studio-app/tests/e2e/admin/manage-accounts.spec.ts b/frontend/svalyn-studio-app/tests/e2e/admin/manage-accounts.spec.ts new file mode 100644 index 00000000..65be3183 --- /dev/null +++ b/frontend/svalyn-studio-app/tests/e2e/admin/manage-accounts.spec.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022, 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { test } from '../../fixture'; + +test.describe('Manage accounts', () => { + test('should let me view the accounts created', async ({ loginPage, userMenu }) => { + await loginPage.goto(); + await loginPage.login('admin', 'password'); + + await userMenu.open(); + await userMenu.gotoAdmin(); + }); +}); diff --git a/frontend/svalyn-studio-app/tests/login/login.spec.ts b/frontend/svalyn-studio-app/tests/e2e/authentication/authenticate.spec.ts similarity index 91% rename from frontend/svalyn-studio-app/tests/login/login.spec.ts rename to frontend/svalyn-studio-app/tests/e2e/authentication/authenticate.spec.ts index bee1c172..0448dc3c 100644 --- a/frontend/svalyn-studio-app/tests/login/login.spec.ts +++ b/frontend/svalyn-studio-app/tests/e2e/authentication/authenticate.spec.ts @@ -18,9 +18,9 @@ */ import { expect } from '@playwright/test'; -import { test } from '../fixture'; +import { test } from '../../fixture'; -test.describe('Login view', () => { +test.describe('Authenticate', () => { test('should let me log in with Github', async ({ page }) => { await page.goto('http://localhost:5173/login'); @@ -38,14 +38,15 @@ test.describe('Login view', () => { await expect(page).toHaveURL('http://localhost:5173'); }); - test('should let me log in and then log out', async ({ page, loginPage }) => { + test('should let me log in and then log out', async ({ page, loginPage, userMenu }) => { await loginPage.goto(); await loginPage.login('admin', 'password'); await expect(page).toHaveURL('http://localhost:5173'); - await page.getByTestId('user-menu-avatar').click(); - await page.getByRole('menuitem', { name: 'Sign out' }).click(); + await userMenu.open(); + await userMenu.logout(); + await expect(page).toHaveURL('http://localhost:5173/login'); }); diff --git a/frontend/svalyn-studio-app/tests/organization/new-organization.spec.ts b/frontend/svalyn-studio-app/tests/e2e/organization/create-organization.spec.ts similarity index 96% rename from frontend/svalyn-studio-app/tests/organization/new-organization.spec.ts rename to frontend/svalyn-studio-app/tests/e2e/organization/create-organization.spec.ts index 7bdfb590..64d2a7f8 100644 --- a/frontend/svalyn-studio-app/tests/organization/new-organization.spec.ts +++ b/frontend/svalyn-studio-app/tests/e2e/organization/create-organization.spec.ts @@ -18,9 +18,9 @@ */ import { expect } from '@playwright/test'; -import { test } from '../fixture'; +import { test } from '../../fixture'; -test.describe('New organization view', () => { +test.describe('Create organization', () => { test.beforeEach(async ({ loginPage }) => { await loginPage.goto(); await loginPage.loginAsAdmin(); diff --git a/frontend/svalyn-studio-app/tests/e2e/organization/update-organization-members.spec.ts b/frontend/svalyn-studio-app/tests/e2e/organization/update-organization-members.spec.ts new file mode 100644 index 00000000..85d32fab --- /dev/null +++ b/frontend/svalyn-studio-app/tests/e2e/organization/update-organization-members.spec.ts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022, 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { expect } from '@playwright/test'; +import { test } from '../../fixture'; +import { Organization } from '../../fixture.types'; + +test.describe('Update organization members', () => { + let organization: Organization; + + test.beforeEach(async ({ loginPage, newOrganizationPage }) => { + await loginPage.goto(); + await loginPage.loginAsAdmin(); + + await newOrganizationPage.goto(); + organization = await newOrganizationPage.createOrganization(); + }); + + test.afterEach(async ({ organizationSettingsPage }) => { + await organizationSettingsPage.goto(organization.identifier); + await organizationSettingsPage.delete(); + }); + + test('should have the creator of the project has a member', async ({ organizationMembersPage, page }) => { + await organizationMembersPage.goto(organization.identifier); + await expect(page.getByRole('cell', { name: 'Admin Admin' })).toBeVisible(); + }); +}); diff --git a/frontend/svalyn-studio-app/tests/organization/organization.spec.ts b/frontend/svalyn-studio-app/tests/e2e/organization/update-organization.spec.ts similarity index 82% rename from frontend/svalyn-studio-app/tests/organization/organization.spec.ts rename to frontend/svalyn-studio-app/tests/e2e/organization/update-organization.spec.ts index ea70e7ab..90f04281 100644 --- a/frontend/svalyn-studio-app/tests/organization/organization.spec.ts +++ b/frontend/svalyn-studio-app/tests/e2e/organization/update-organization.spec.ts @@ -18,10 +18,10 @@ */ import { expect } from '@playwright/test'; -import { test } from '../fixture'; -import { Organization } from '../fixture.types'; +import { test } from '../../fixture'; +import { Organization } from '../../fixture.types'; -test.describe('Organization settings view', () => { +test.describe('Update organization', () => { let organization: Organization; test.beforeEach(async ({ loginPage, newOrganizationPage }) => { @@ -37,14 +37,12 @@ test.describe('Organization settings view', () => { await organizationSettingsPage.delete(); }); - test('should let me update the organization name', async ({ page }) => { + test('should let me update the organization name', async ({ page, organizationSettingsPage }) => { await page.goto(`http://localhost:5173/orgs/${organization.identifier}`); await expect(page.getByRole('heading', { name: organization.name })).toBeVisible(); await page.getByRole('tab').filter({ hasText: 'SETTINGS' }).click(); - await page.getByRole('textbox', { name: 'Organization Name' }).fill(`Renamed ${organization.name}`); - await page.getByRole('button', { name: 'RENAME' }).click(); - await expect(page.getByRole('heading', { name: `Renamed ${organization.name}` })).toBeVisible(); + await organizationSettingsPage.rename(`Renamed ${organization.name}`); }); }); diff --git a/frontend/svalyn-studio-app/tests/project/new-project.spec.ts b/frontend/svalyn-studio-app/tests/e2e/project/create-project.spec.ts similarity index 68% rename from frontend/svalyn-studio-app/tests/project/new-project.spec.ts rename to frontend/svalyn-studio-app/tests/e2e/project/create-project.spec.ts index 1a18ae57..b02bf6d6 100644 --- a/frontend/svalyn-studio-app/tests/project/new-project.spec.ts +++ b/frontend/svalyn-studio-app/tests/e2e/project/create-project.spec.ts @@ -18,10 +18,10 @@ */ import { expect } from '@playwright/test'; -import { test } from '../fixture'; -import { Organization } from '../fixture.types'; +import { test } from '../../fixture'; +import { Organization } from '../../fixture.types'; -test.describe('New project view', () => { +test.describe('Create project', () => { let organization: Organization; test.beforeEach(async ({ loginPage, newOrganizationPage }) => { @@ -37,19 +37,12 @@ test.describe('New project view', () => { await organizationSettingsPage.delete(); }); - test('should let me create a project', async ({ page, browserName }) => { + test('should let me create a project', async ({ page, organizationPage }) => { + await organizationPage.goto(organization.identifier); await page.goto(`http://localhost:5173/orgs/${organization.identifier}`); - await expect(page.getByRole('heading', { name: organization.name })).toBeVisible(); + const project = await organizationPage.createProject(); - await page.getByRole('button', { name: 'NEW PROJECT' }).click(); - await page.getByRole('textbox', { name: 'Project Name' }).fill(`New project view ${browserName}`); - await page.getByRole('textbox', { name: 'Project Identifier' }).fill(`creating-project-view-${browserName}`); - await page - .getByRole('textbox', { name: 'Project Description' }) - .fill(`New project view description ${browserName}`); - await page.getByRole('button', { name: 'CREATE' }).click(); - - await expect(page).toHaveURL(`http://localhost:5173/projects/creating-project-view-${browserName}`); + await expect(page).toHaveURL(`http://localhost:5173/projects/${project.identifier}`); }); }); diff --git a/frontend/svalyn-studio-app/tests/fixture.ts b/frontend/svalyn-studio-app/tests/fixture.ts index 49cd746d..2399daf1 100644 --- a/frontend/svalyn-studio-app/tests/fixture.ts +++ b/frontend/svalyn-studio-app/tests/fixture.ts @@ -18,16 +18,40 @@ */ import { test as base } from '@playwright/test'; import { Fixture } from './fixture.types'; +import { HomePage } from './pages/HomePage'; import { LoginPage } from './pages/LoginPage'; import { NewOrganizationPage } from './pages/NewOrganizationPage'; -import { OrganizationPage } from './pages/OrganizationPage'; -import { OrganizationSettingsPage } from './pages/OrganizationSettingsPage'; +import { AdminAccountsPage } from './pages/admin/AdminAccountsPage'; +import { AdminNewAccountPage } from './pages/admin/AdminNewAccountPage'; +import { OrganizationMembersPage } from './pages/organization/OrganizationMembersPage'; +import { OrganizationPage } from './pages/organization/OrganizationPage'; +import { OrganizationSettingsPage } from './pages/organization/OrganizationSettingsPage'; +import { InvitationsPage } from './pages/profile/InvitationsPage'; +import { ProfilePage } from './pages/profile/ProfilePage'; +import { SettingsPage } from './pages/profile/SettingsPage'; +import { UserMenu } from './widgets/UserMenu'; export const test = base.extend({ loginPage: async ({ page }, use) => { await use(new LoginPage(page)); }, + homePage: async ({ page }, use) => { + await use(new HomePage(page)); + }, + + profilePage: async ({ page }, use) => { + await use(new ProfilePage(page, '')); + }, + + invitationsPage: async ({ page }, use) => { + await use(new InvitationsPage(page)); + }, + + settingsPage: async ({ page }, use) => { + await use(new SettingsPage(page)); + }, + newOrganizationPage: async ({ page }, use) => { await use(new NewOrganizationPage(page)); }, @@ -36,7 +60,23 @@ export const test = base.extend({ await use(new OrganizationPage(page, '')); }, + organizationMembersPage: async ({ page }, use) => { + await use(new OrganizationMembersPage(page, '')); + }, + organizationSettingsPage: async ({ page }, use) => { await use(new OrganizationSettingsPage(page, '')); }, + + adminAccountsPage: async ({ page }, use) => { + await use(new AdminAccountsPage(page)); + }, + + adminNewAccountPage: async ({ page }, use) => { + await use(new AdminNewAccountPage(page)); + }, + + userMenu: async ({ page }, use) => { + await use(new UserMenu(page)); + }, }); diff --git a/frontend/svalyn-studio-app/tests/fixture.types.ts b/frontend/svalyn-studio-app/tests/fixture.types.ts index 6dc63fa8..36a4bf32 100644 --- a/frontend/svalyn-studio-app/tests/fixture.types.ts +++ b/frontend/svalyn-studio-app/tests/fixture.types.ts @@ -17,16 +17,33 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { HomePage } from './pages/HomePage'; import { LoginPage } from './pages/LoginPage'; import { NewOrganizationPage } from './pages/NewOrganizationPage'; -import { OrganizationPage } from './pages/OrganizationPage'; -import { OrganizationSettingsPage } from './pages/OrganizationSettingsPage'; +import { AdminAccountsPage } from './pages/admin/AdminAccountsPage'; +import { AdminNewAccountPage } from './pages/admin/AdminNewAccountPage'; +import { OrganizationMembersPage } from './pages/organization/OrganizationMembersPage'; +import { OrganizationPage } from './pages/organization/OrganizationPage'; +import { OrganizationSettingsPage } from './pages/organization/OrganizationSettingsPage'; +import { InvitationsPage } from './pages/profile/InvitationsPage'; +import { ProfilePage } from './pages/profile/ProfilePage'; +import { SettingsPage } from './pages/profile/SettingsPage'; +import { UserMenu } from './widgets/UserMenu'; export type Fixture = { loginPage: LoginPage; + homePage: HomePage; + profilePage: ProfilePage; + invitationsPage: InvitationsPage; + settingsPage: SettingsPage; newOrganizationPage: NewOrganizationPage; organizationPage: OrganizationPage; + organizationMembersPage: OrganizationMembersPage; organizationSettingsPage: OrganizationSettingsPage; + adminAccountsPage: AdminAccountsPage; + adminNewAccountPage: AdminNewAccountPage; + + userMenu: UserMenu; }; export type Organization = { diff --git a/frontend/svalyn-studio-app/tests/pages/HomePage.ts b/frontend/svalyn-studio-app/tests/pages/HomePage.ts new file mode 100644 index 00000000..43fcd755 --- /dev/null +++ b/frontend/svalyn-studio-app/tests/pages/HomePage.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { Page } from '@playwright/test'; + +export class HomePage { + constructor(public readonly page: Page) {} + + async goto() { + await this.page.goto('http://localhost:5173/'); + } +} diff --git a/frontend/svalyn-studio-app/tests/pages/admin/AdminAccountsPage.ts b/frontend/svalyn-studio-app/tests/pages/admin/AdminAccountsPage.ts new file mode 100644 index 00000000..8c9be6ff --- /dev/null +++ b/frontend/svalyn-studio-app/tests/pages/admin/AdminAccountsPage.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { Page } from '@playwright/test'; + +export class AdminAccountsPage { + constructor(public readonly page: Page) {} + + async goto() { + await this.page.goto('http://localhost:5173/admin/accounts'); + } +} diff --git a/frontend/svalyn-studio-app/tests/pages/admin/AdminNewAccountPage.ts b/frontend/svalyn-studio-app/tests/pages/admin/AdminNewAccountPage.ts new file mode 100644 index 00000000..fee29401 --- /dev/null +++ b/frontend/svalyn-studio-app/tests/pages/admin/AdminNewAccountPage.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { Page } from '@playwright/test'; + +export class AdminNewAccountPage { + constructor(public readonly page: Page) {} + + async goto() { + await this.page.goto('http://localhost:5173/admin/new/account'); + } +} diff --git a/frontend/svalyn-studio-app/tests/pages/OrganizationPage.ts b/frontend/svalyn-studio-app/tests/pages/organization/OrganizationMembersPage.ts similarity index 95% rename from frontend/svalyn-studio-app/tests/pages/OrganizationPage.ts rename to frontend/svalyn-studio-app/tests/pages/organization/OrganizationMembersPage.ts index d0ee31c3..e0d51f56 100644 --- a/frontend/svalyn-studio-app/tests/pages/OrganizationPage.ts +++ b/frontend/svalyn-studio-app/tests/pages/organization/OrganizationMembersPage.ts @@ -19,11 +19,11 @@ import { Page } from '@playwright/test'; -export class OrganizationPage { +export class OrganizationMembersPage { constructor(public readonly page: Page, public identifier: string) {} async goto(identifier: string) { this.identifier = identifier; - await this.page.goto(`http://localhost:5173/orgs/${this.identifier}`); + await this.page.goto(`http://localhost:5173/orgs/${this.identifier}/members`); } } diff --git a/frontend/svalyn-studio-app/tests/pages/organization/OrganizationPage.ts b/frontend/svalyn-studio-app/tests/pages/organization/OrganizationPage.ts new file mode 100644 index 00000000..4db7bdde --- /dev/null +++ b/frontend/svalyn-studio-app/tests/pages/organization/OrganizationPage.ts @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { Page, expect } from '@playwright/test'; +import { Project } from '../../fixture.types'; + +export class OrganizationPage { + constructor(public readonly page: Page, public identifier: string) {} + + async goto(identifier: string) { + this.identifier = identifier; + await this.page.goto(`http://localhost:5173/orgs/${this.identifier}`); + } + + async createProject(): Promise { + const identifier = await this.page.evaluate(() => crypto.randomUUID()); + const name = `Project ${identifier}`; + + await this.page.getByRole('button', { name: 'NEW PROJECT' }).click(); + await this.page.getByRole('textbox', { name: 'Project Name' }).fill(name); + await this.page.getByRole('textbox', { name: 'Project Identifier' }).fill(identifier); + await this.page + .getByRole('textbox', { name: 'Project Description' }) + .fill(`The description of the project "${name}"`); + await this.page.getByRole('button', { name: 'CREATE' }).click(); + + await expect(this.page).toHaveURL(`http://localhost:5173/projects/${identifier}`); + + return new Promise((resolve) => { + resolve({ identifier, name }); + }); + } +} diff --git a/frontend/svalyn-studio-app/tests/pages/OrganizationSettingsPage.ts b/frontend/svalyn-studio-app/tests/pages/organization/OrganizationSettingsPage.ts similarity index 85% rename from frontend/svalyn-studio-app/tests/pages/OrganizationSettingsPage.ts rename to frontend/svalyn-studio-app/tests/pages/organization/OrganizationSettingsPage.ts index 74f12c42..fe167b18 100644 --- a/frontend/svalyn-studio-app/tests/pages/OrganizationSettingsPage.ts +++ b/frontend/svalyn-studio-app/tests/pages/organization/OrganizationSettingsPage.ts @@ -27,8 +27,14 @@ export class OrganizationSettingsPage { await this.page.goto(`http://localhost:5173/orgs/${this.identifier}/settings`); } + async rename(newName: string) { + await this.page.getByRole('textbox', { name: 'Organization Name' }).fill(newName); + await this.page.getByRole('button', { name: 'RENAME' }).click(); + + await expect(this.page.getByRole('heading', { name: newName })).toBeVisible(); + } + async delete() { - await this.page.goto(`http://localhost:5173/orgs/${this.identifier}/settings`); await this.page.getByRole('button', { name: 'DELETE ORGANIZATION' }).click(); await this.page.getByRole('button', { name: 'DELETE' }).click(); diff --git a/frontend/svalyn-studio-app/tests/pages/profile/InvitationsPage.ts b/frontend/svalyn-studio-app/tests/pages/profile/InvitationsPage.ts new file mode 100644 index 00000000..acb2d841 --- /dev/null +++ b/frontend/svalyn-studio-app/tests/pages/profile/InvitationsPage.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { Page } from '@playwright/test'; + +export class InvitationsPage { + constructor(public readonly page: Page) {} + + async goto() { + await this.page.goto('http://localhost:5173/invitations'); + } +} diff --git a/frontend/svalyn-studio-app/tests/pages/profile/ProfilePage.ts b/frontend/svalyn-studio-app/tests/pages/profile/ProfilePage.ts new file mode 100644 index 00000000..b884e43d --- /dev/null +++ b/frontend/svalyn-studio-app/tests/pages/profile/ProfilePage.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { Page } from '@playwright/test'; + +export class ProfilePage { + constructor(public readonly page: Page, public username: string) {} + + async goto(username: string) { + this.username = username; + await this.page.goto(`http://localhost:5173/profiles/${this.username}`); + } +} diff --git a/frontend/svalyn-studio-app/tests/pages/profile/SettingsPage.ts b/frontend/svalyn-studio-app/tests/pages/profile/SettingsPage.ts new file mode 100644 index 00000000..3fe9171d --- /dev/null +++ b/frontend/svalyn-studio-app/tests/pages/profile/SettingsPage.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { Page } from '@playwright/test'; + +export class SettingsPage { + constructor(public readonly page: Page) {} + + async goto() { + await this.page.goto(`http://localhost:5173/settings`); + } +} diff --git a/frontend/svalyn-studio-app/tests/widgets/UserMenu.ts b/frontend/svalyn-studio-app/tests/widgets/UserMenu.ts new file mode 100644 index 00000000..19ee5e22 --- /dev/null +++ b/frontend/svalyn-studio-app/tests/widgets/UserMenu.ts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { Page } from '@playwright/test'; + +export class UserMenu { + constructor(public readonly page: Page) {} + + async open() { + await this.page.getByTestId('user-menu-avatar').click(); + } + + async gotoDashboard() { + await this.page.getByRole('menuitem', { name: 'Dashboard' }).click(); + } + + async gotoProfile() { + await this.page.getByRole('menuitem', { name: 'Profile' }).click(); + } + + async gotoInvitations() { + await this.page.getByRole('menuitem', { name: 'Invitations' }).click(); + } + + async gotoSettings() { + await this.page.getByRole('menuitem', { name: 'Settings' }).click(); + } + + async gotoAdmin() { + await this.page.getByRole('menuitem', { name: 'Admin Panel' }).click(); + } + + async gotoHelp() { + await this.page.getByRole('menuitem', { name: 'Help' }).click(); + } + + async logout() { + await this.page.getByRole('menuitem', { name: 'Sign out' }).click(); + } + + async close() { + await this.page.press('[data-testid="user-menu-avatar"]', 'Esc'); + } +}