diff --git a/playwright/e2e/create-empty-form.spec.ts b/playwright/e2e/create-empty-form.spec.ts index eca66cc272..99e284a807 100644 --- a/playwright/e2e/create-empty-form.spec.ts +++ b/playwright/e2e/create-empty-form.spec.ts @@ -1,84 +1,58 @@ /** - * @copyright Copyright (c) 2024 Ferdinand Thiessen - * - * @author Ferdinand Thiessen - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * + * SPDX-FileCopyrightText: 2024 Ferdinand Thiessen + * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { expect } from '@playwright/test' -import { test } from '../fixtures/random-user' + +import { expect, mergeTests } from '@playwright/test' +import { test as randomUserTest } from '../support/fixtures/random-user' +import { test as appNavigationTest } from '../support/fixtures/navigation' +import { test as formTest } from '../support/fixtures/form' + +const test = mergeTests(randomUserTest, appNavigationTest, formTest) test.beforeEach(async ({ page }) => { await page.goto('apps/forms') await page.waitForURL(/apps\/forms$/) }) -test('It shows the empty content', async ({ page }) => { - await expect(page.getByText('No forms created yet')).toBeVisible() - await expect(page.getByRole('button', { name: 'Create a form' })).toBeVisible() -}) +test.describe('No forms created - empty content', () => { + test('It shows the empty content', async ({ page }) => { + await expect(page.getByText('No forms created yet')).toBeVisible() + await expect( + page.getByRole('button', { name: 'Create a form' }), + ).toBeVisible() + }) -test('Use button to create new form', async ({ page }) => { - await page.getByRole('button', { name: 'Create a form' }).click() + test('Use button to create new form', async ({ page, appNavigation }) => { + const oldNumber = (await appNavigation.ownFormsLocator.all()).length - await page.waitForURL(/apps\/forms\/.+/) + await page.getByRole('button', { name: 'Create a form' }).click() + await page.waitForURL(/apps\/forms\/.+/) - // check we are in edit mode by default and the heading is focussed - await expect(page.locator('h2 textarea')).toBeVisible() - await expect(page.locator('h2 textarea')).toBeFocused() + const newNumber = (await appNavigation.ownFormsLocator.all()).length + expect(newNumber - oldNumber).toBe(1) + }) }) -test('Use app navigation to create new form', async ({ page }) => { - await page.getByRole('navigation') - .getByRole('button', { name: 'New form' }) - .click() - - await page.waitForURL(/apps\/forms\/.+/) +test('Use app navigation to create new form', async ({ appNavigation, form }) => { + await appNavigation.clickNewForm() // check we are in edit mode by default and the heading is focussed - await expect(page.locator('h2 textarea')).toBeVisible() - await expect(page.locator('h2 textarea')).toBeFocused() + await expect(form.titleField).toBeFocused() }) -test('Form name updated in navigation', async ({ page }) => { - // Create a form - await page - .getByRole('navigation') - .getByRole('button', { name: 'New form' }) - .click() - - await page.waitForURL(/apps\/forms\/.+/) +test('Form name updated in navigation', async ({ appNavigation, form }) => { + await appNavigation.clickNewForm() // check we are in edit mode by default and the heading is focussed - await expect(page.locator('h2 textarea')).toBeVisible() - await expect(page.locator('h2 textarea')).toBeFocused() + await expect(form.titleField).toBeFocused() // check the form exists in the navigation - await page - .getByRole('list', { name: 'Your forms' }) - .getByRole('link', { name: 'New form' }) - .isVisible() + await expect(appNavigation.getOwnForm('New form')).toBeVisible() // Update the title - await page.locator('h2 textarea').fill('My example form') + await form.fillTitle('My example form') // See the title is updated - await page - .getByRole('list', { name: 'Your forms' }) - .getByRole('link', { name: 'My example form' }) - .isVisible() + await expect(appNavigation.getOwnForm('My example form')).toBeVisible() }) diff --git a/playwright/support/fixtures/form.ts b/playwright/support/fixtures/form.ts new file mode 100644 index 0000000000..d06429870f --- /dev/null +++ b/playwright/support/fixtures/form.ts @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: 2024 Ferdinand Thiessen + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { test as baseTest } from '@playwright/test' +import { FormSection } from '../sections/FormSection' + +interface FormFixture { + form: FormSection +} + +export const test = baseTest.extend({ + form: async ({ page }, use) => { + const form = new FormSection(page) + await use(form) + }, +}) diff --git a/playwright/support/fixtures/navigation.ts b/playwright/support/fixtures/navigation.ts new file mode 100644 index 0000000000..253225f7c0 --- /dev/null +++ b/playwright/support/fixtures/navigation.ts @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: 2024 Ferdinand Thiessen + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { test as baseTest } from '@playwright/test' +import { AppNavigationSection } from '../sections/AppNavigationSection' + +interface AppNavigationFixture { + appNavigation: AppNavigationSection +} + +export const test = baseTest.extend({ + appNavigation: async ({ page }, use) => { + const appNavigation = new AppNavigationSection(page) + await use(appNavigation) + }, +}) diff --git a/playwright/support/sections/AppNavigationSection.ts b/playwright/support/sections/AppNavigationSection.ts new file mode 100644 index 0000000000..27589cafcd --- /dev/null +++ b/playwright/support/sections/AppNavigationSection.ts @@ -0,0 +1,43 @@ +/** + * SPDX-FileCopyrightText: 2024 Ferdinand Thiessen + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Locator, Page } from '@playwright/test' + +export class AppNavigationSection { + public readonly navigationLocator: Locator + public readonly newFormLocator: Locator + public readonly ownFormsLocator: Locator + public readonly sharedFormsLocator: Locator + + // eslint-disable-next-line no-useless-constructor + constructor(public readonly page: Page) { + this.navigationLocator = this.page.getByRole('navigation', { + name: 'Forms navigation', + }) + this.newFormLocator = this.navigationLocator.getByRole('button', { + name: 'New form', + }) + this.ownFormsLocator = this.navigationLocator + .getByRole('list', { name: 'Your forms' }) + .getByRole('listitem') + this.sharedFormsLocator = this.navigationLocator + .getByRole('button', { name: 'Shared forms' }) + .getByRole('listitem') + } + + public async clickNewForm(): Promise { + await this.newFormLocator.click() + } + + public async openArchivedForms(): Promise { + await this.navigationLocator + .getByRole('button', { name: 'Archived forms' }) + .click() + } + + public getOwnForm(name: string | RegExp): Locator { + return this.ownFormsLocator.getByRole('link', { name }) + } +} diff --git a/playwright/support/sections/FormSection.ts b/playwright/support/sections/FormSection.ts new file mode 100644 index 0000000000..c1e806db82 --- /dev/null +++ b/playwright/support/sections/FormSection.ts @@ -0,0 +1,23 @@ +/** + * SPDX-FileCopyrightText: 2024 Ferdinand Thiessen + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Locator, Page } from '@playwright/test' + +export class FormSection { + public readonly mainContent: Locator + public readonly titleField: Locator + + // eslint-disable-next-line no-useless-constructor + constructor(public readonly page: Page) { + this.mainContent = this.page.getByRole('main') + this.titleField = this.mainContent.getByRole('textbox', { + name: 'Form title', + }) + } + + public async fillTitle(text: string): Promise { + await this.titleField.fill(text) + } +} diff --git a/playwright/support/utils/session.ts b/playwright/support/utils/session.ts index 5eb9b739f7..950ca08990 100644 --- a/playwright/support/utils/session.ts +++ b/playwright/support/utils/session.ts @@ -14,25 +14,36 @@ import { expect, type APIRequestContext } from '@playwright/test' * @param options.user User to use for executing the command * @param options.rejectOnError Reject the returned promise in case of non-zero exit code */ -export async function runShell(command: string, options?: { user?: string, rejectOnError?: boolean, env?: Record}) { +export async function runShell( + command: string, + options?: { + user?: string + rejectOnError?: boolean + env?: Record + }, +) { const containerName = 'nextcloud-cypress-tests_forms' const container = docker.getContainer(containerName) const exec = await container.exec({ Cmd: ['sh', '-c', command], - Env: Object.entries(options?.env ?? {}).map(([name, value]) => `${name}=${value}`), + Env: Object.entries(options?.env ?? {}).map( + ([name, value]) => `${name}=${value}`, + ), User: options?.user, AttachStderr: true, AttachStdout: true, }) - const stream = await exec.start({ }) + const stream = await exec.start({}) return new Promise((resolve, reject) => { let data = '' - stream.on('data', (chunk: string) => { data += chunk }) + stream.on('data', (chunk: string) => { + data += chunk + }) stream.on('error', (error: unknown) => reject(error)) stream.on('end', async () => { - const inspect = await exec.inspect({ }) + const inspect = await exec.inspect({}) if (options?.rejectOnError !== false && inspect.ExitCode) { reject(data) } else { @@ -49,14 +60,14 @@ export async function runShell(command: string, options?: { user?: string, rejec * @param options.env Process environment to pass * @param options.rejectOnError Reject the returned promise in case of non-zero exit code */ -export async function runOCC(command: string, options?: { env?: Record, rejectOnError?: boolean }) { - return await runShell( - `php ./occ ${command}`, - { - ...options, - user: 'www-data', - }, - ) +export async function runOCC( + command: string, + options?: { env?: Record; rejectOnError?: boolean }, +) { + return await runShell(`php ./occ ${command}`, { + ...options, + user: 'www-data', + }) } /** @@ -72,7 +83,11 @@ export function restoreDatabase() { * @param user The username to login * @param password The password to login */ -export async function login(request: APIRequestContext, user: string, password: string) { +export async function login( + request: APIRequestContext, + user: string, + password: string, +) { const tokenResponse = await request.get('./csrftoken') const requesttoken = (await tokenResponse.json()).token