From d7a1440a907b54e3a6442bbcc947a9561f9f261b Mon Sep 17 00:00:00 2001 From: Cody Zuschlag Date: Thu, 15 Feb 2024 17:06:26 +0100 Subject: [PATCH] e2e test presentation preferences + improve tests --- src/components/NoteEditor.tsx | 7 +- .../PresentationPreferencesEditor.tsx | 5 +- src/pages/error.e2e.ts | 6 +- src/pages/presentation.e2e.ts | 15 ++ src/pages/upload.e2e.ts | 155 ++++++++++++++++-- src/pages/viewer.e2e.ts | 3 + uno.config.ts | 14 ++ 7 files changed, 186 insertions(+), 19 deletions(-) diff --git a/src/components/NoteEditor.tsx b/src/components/NoteEditor.tsx index 206c30d..fbd3abd 100644 --- a/src/components/NoteEditor.tsx +++ b/src/components/NoteEditor.tsx @@ -30,8 +30,8 @@ export default function NoteEditor({ ); return ( - <> -
+
  • +
    {pageIndices.map((pageIndex, indicesIndex) => ( {`Slide ))}
    @@ -90,6 +91,6 @@ export default function NoteEditor({ {markdown}
  • - + ); } diff --git a/src/components/PresentationPreferencesEditor.tsx b/src/components/PresentationPreferencesEditor.tsx index 1b11707..b85f919 100644 --- a/src/components/PresentationPreferencesEditor.tsx +++ b/src/components/PresentationPreferencesEditor.tsx @@ -98,7 +98,8 @@ export default function PresentationPreferencesEditor({ }} /> -
    +
    Speaker notes:
    +
    + ); diff --git a/src/pages/error.e2e.ts b/src/pages/error.e2e.ts index 675832f..6be599f 100644 --- a/src/pages/error.e2e.ts +++ b/src/pages/error.e2e.ts @@ -29,7 +29,11 @@ test('displays an error when the presentation does not exist', async ({ ); expect(errorLogs.length).toBeGreaterThanOrEqual(1); - expect(errorLogs[0]).toMatch( + const filteredLogs = errorLogs.filter((log) => + log.includes("Error: Presentation 'does-not-exist' does not exist"), + ); + expect(filteredLogs.length).toBeGreaterThanOrEqual(1); + expect(filteredLogs[0]).toMatch( "Error: Presentation 'does-not-exist' does not exist", ); }); diff --git a/src/pages/presentation.e2e.ts b/src/pages/presentation.e2e.ts index d89aac3..01d83bf 100644 --- a/src/pages/presentation.e2e.ts +++ b/src/pages/presentation.e2e.ts @@ -128,4 +128,19 @@ test('can add and clear reactions', async ({coverage}, testInfo) => { ).toHaveCount(0, {timeout: 500}); }); +test('generates a session id if not set', async ({ + page, + // @ts-expect-error activate coverage + coverage, +}) => { + await page.goto(`/p/${presentationId}`); + + await expect(page.getByRole('img', {name: 'Slide page 1'})).toBeVisible(); + + const pageUrl = new URL(page.url()); + + // Expect at least 1 character for the session id + expect(pageUrl.searchParams.get('session')).toMatch(/.+/); +}); + // TODO test confetti, but how? diff --git a/src/pages/upload.e2e.ts b/src/pages/upload.e2e.ts index 3b48289..1af35f8 100644 --- a/src/pages/upload.e2e.ts +++ b/src/pages/upload.e2e.ts @@ -1,23 +1,14 @@ import {test, expect} from '../test/login-fixture'; import {generateId} from '../test/id'; -test('upload button appears after signing in', async ({page, loginPage}) => { - test.setTimeout(30_000); - await page.goto('/'); - await expect(page.getByRole('button', {name: /upload/i})).not.toBeVisible(); - - await loginPage.goto(); - await loginPage.signIn(); - await loginPage.signInComplete(); - await page.goto('/'); - await expect(page.getByRole('button', {name: /upload/i})).toBeVisible({ - timeout: 20_000, - }); -}); +test.describe.configure({mode: 'parallel'}); test('can upload and view presentation', async ({page, loginPage}) => { test.setTimeout(60_000); + await page.goto('/'); + await expect(page.getByRole('button', {name: /upload/i})).not.toBeVisible(); + await loginPage.goto(); await loginPage.signIn(); await loginPage.signInComplete(); @@ -68,3 +59,141 @@ test('can upload and view presentation', async ({page, loginPage}) => { await expect(page3).toBeVisible(); await expect(page3).toHaveScreenshot('page-3.png'); }); + +test('can edit presentation settings', async ({page, loginPage}) => { + test.setTimeout(60_000); + + await loginPage.goto(); + await loginPage.signIn(); + await loginPage.signInComplete(); + + const presentationName = `e2e test - ${generateId()}`; + await page.goto('/'); + await page.getByRole('button', {name: /upload/i}).click(); + await page + .getByRole('button', { + name: /drag 'n' drop/i, + }) + .locator('input') + .setInputFiles('./src/test/pdf/test.pdf'); + await expect(page.getByText(/done/i)).toBeVisible({timeout: 20_000}); + await page.getByLabel(/title/i).fill(presentationName); + await expect(page.getByText(/saving/i)).toBeVisible(); + await expect(page.getByText(/saving/i)).not.toBeVisible(); + await page.getByRole('button', {name: /slidr/i}).click(); + + // Find the presentation in the list + const presentation = page + .getByRole('list', { + name: /presentations/i, + }) + .getByRole('listitem') + .filter({ + has: page.getByText(presentationName), + }) + .first(); + await expect(presentation).toBeVisible(); + + // Can edit my presentation + const editButton = presentation.getByRole('button', {name: 'edit'}); + await expect(editButton).toBeVisible(); + await editButton.click(); + + await expect(page.getByText('Presentation Settings')).toBeVisible(); + await expect(page.getByRole('img', {name: 'Slide 1'})).toBeVisible(); + const titleTextBox = page.getByLabel('Title:'); + await expect(titleTextBox).toBeVisible(); + const editedPresentationName = `rename - ${generateId()}`; + await titleTextBox.fill(editedPresentationName); + + // Add a list into slide 1 notes + const page1Notes = page + .getByRole('list', {name: /speaker notes/i}) + .getByRole('listitem') + .filter({ + has: page.getByText(/page 1/i), + }) + .first(); + await page1Notes.getByRole('textbox').fill('* abc\n* def'); + await expect(page1Notes.getByRole('listitem').getByText('abc')).toBeVisible(); + await expect(page1Notes.getByRole('listitem').getByText('def')).toBeVisible(); + + // Add a header into slide 2 notes + const page2Notes = page + .getByRole('list', {name: /speaker notes/i}) + .getByRole('listitem') + .filter({ + has: page.getByText(/page 2/i), + }) + .first(); + await page2Notes.getByRole('textbox').fill('# title'); + await expect(page2Notes.getByRole('heading', {name: 'title'})).toBeVisible(); + + // Copy slide 3 into slide 2 notes + const page3Notes = page + .getByRole('list', {name: /speaker notes/i}) + .getByRole('listitem') + .filter({ + has: page.getByText(/page 3/i), + }) + .first(); + const copyPreviousButton = page3Notes + .getByRole('button') + .and(page3Notes.getByTitle('Copy previous')); + await expect(copyPreviousButton).toBeVisible(); + await copyPreviousButton.click(); + const pages2And3Notes = page + .getByRole('list', {name: /speaker notes/i}) + .getByRole('listitem') + .filter({ + has: page.getByText(/pages 2 - 3/i), + }) + .first(); + await expect( + pages2And3Notes.getByRole('img', {name: 'Slide 2'}), + ).toBeVisible(); + await expect( + pages2And3Notes.getByRole('img', {name: 'Slide 3'}), + ).toBeVisible(); + + // Wait for the saving to be done + await expect(page.getByText(/saving/i)).toBeVisible(); + await expect(page.getByText(/saving/i)).not.toBeVisible(); + + // Find the presentation in the list + await page.getByRole('button', {name: /slidr/i}).click(); + const editedPresentation = page + .getByRole('list', { + name: /presentations/i, + }) + .getByRole('listitem') + .filter({ + has: page.getByText(editedPresentationName), + }) + .first(); + await expect(editedPresentation).toBeVisible(); + + // Can edit my presentation + const presentButton = editedPresentation.getByRole('button', { + name: 'present', + }); + await expect(presentButton).toBeVisible(); + await presentButton.click(); + + const speakerButton = page.getByRole('button', {name: 'speaker'}); + const popupPromise = page.waitForEvent('popup'); + await speakerButton.click(); + const popup = await popupPromise; + await popup.waitForLoadState(); + + await expect(popup.getByText('Slide: 1')).toBeVisible(); + await expect(popup.getByRole('listitem').getByText('abc')).toBeVisible(); + + await popup.getByRole('button', {name: 'next'}).click(); + await expect(popup.getByText('Slide: 2')).toBeVisible(); + await expect(popup.getByRole('heading', {name: 'title'})).toBeVisible(); + + await popup.getByRole('button', {name: 'next'}).click(); + await expect(popup.getByText('Slide: 3')).toBeVisible(); + await expect(popup.getByRole('heading', {name: 'title'})).toBeVisible(); +}); diff --git a/src/pages/viewer.e2e.ts b/src/pages/viewer.e2e.ts index 0cc8fcf..acd9752 100644 --- a/src/pages/viewer.e2e.ts +++ b/src/pages/viewer.e2e.ts @@ -51,6 +51,9 @@ test('can share with share buttons', async ({ }) => { await page.goto(`/v/${presentationId}?slide=2`); + // Wait for the presentation to load + await expect(page.getByRole('img', {name: 'Slide page 2'})).toBeVisible(); + // Twitter const tweetButton = page.getByRole('link', {name: 'tweet'}); diff --git a/uno.config.ts b/uno.config.ts index 85cdc69..2bbe874 100644 --- a/uno.config.ts +++ b/uno.config.ts @@ -11,6 +11,20 @@ export default defineConfig({ presets: [ presetUno(), presetIcons({ + // Normally we don't have to load iconify collections. + // However, when running playwright tests from vscode, the icons don't get loaded. + // Loading them explicitly seems to fix the problem. + collections: { + tabler: async () => + import('@iconify-json/tabler/icons.json').then((i) => i.default), + // @ts-expect-error the type is being picked up from this json file, maybe it's too large? + 'fluent-emoji-flat': async () => + import('@iconify-json/fluent-emoji-flat/icons.json').then( + (i) => i.default, + ), + 'line-md': async () => + import('@iconify-json/line-md/icons.json').then((i) => i.default), + }, extraProperties: { display: 'inline-block', 'vertical-align': 'middle',