Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up integration tests and add listeners for backend calls #11847

Merged
merged 29 commits into from
Dec 12, 2024
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1a64456
Remove section headers from integration tests
somebody1234 Nov 28, 2024
d5eee57
Add `calls` to E2E tests
somebody1234 Nov 29, 2024
3a22990
Adjust tests
somebody1234 Dec 2, 2024
7f9e042
Fix type errors
somebody1234 Dec 2, 2024
0072570
Use named imports in Dashboard integration tests
somebody1234 Dec 2, 2024
490602e
WIP: `goToSettingsTabActions`
somebody1234 Dec 2, 2024
c7f4ea0
Update `userSettings.spec` to use new API
somebody1234 Dec 2, 2024
b7daa29
Finish updating `userSettings.spec` to use new API
somebody1234 Dec 3, 2024
62a9427
Update `organizationSettings.spec` to use new API
somebody1234 Dec 3, 2024
75efa0a
Switch integration tests to use named imports
somebody1234 Dec 3, 2024
554e174
Add `DrivePageActions.withSearchBar` to switch `assetSearchBar.spec` …
somebody1234 Dec 3, 2024
e049e33
Basic conversion of E2E tests to new structure
somebody1234 Dec 11, 2024
3538721
Fix circular imports in integration tests
somebody1234 Dec 11, 2024
e1cbb0e
Merge branch 'develop' into wip/sb/more-integration-tests
somebody1234 Dec 11, 2024
2e086f0
Fix Playwright imports
somebody1234 Dec 11, 2024
20af42c
Formatting
somebody1234 Dec 11, 2024
9b2d078
Use `page` from `actions.step`
somebody1234 Dec 11, 2024
8179f17
Fix broken integration tests
somebody1234 Dec 11, 2024
abcb91b
Fix integration tests
somebody1234 Dec 11, 2024
78a515a
Merge branch 'develop' into wip/sb/more-integration-tests
somebody1234 Dec 12, 2024
37edeb0
Fix more integration tests
somebody1234 Dec 12, 2024
9f9982a
Fix more integration tests
somebody1234 Dec 12, 2024
cea2aeb
Fix test errors
somebody1234 Dec 12, 2024
a9f45ea
Consistent formatting
somebody1234 Dec 12, 2024
8581f73
Fix lat integration test
somebody1234 Dec 12, 2024
f10d637
Fix
somebody1234 Dec 12, 2024
c796cde
Track backend calls in `organizationSettings.spec`
somebody1234 Dec 12, 2024
10ecb07
Add more docs for integration tests
somebody1234 Dec 12, 2024
6c0db07
Uncomment tests and skip them instead
somebody1234 Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Consistent formatting
somebody1234 committed Dec 12, 2024
commit a9f45ead4a4222c78a33591ab8072c7f7e25d437
43 changes: 15 additions & 28 deletions app/gui/integration-test/dashboard/README.md
Original file line number Diff line number Diff line change
@@ -3,51 +3,38 @@
## Running tests

Execute all commands from the parent directory.
Note that all options can be used in any combination.

```sh
# Run tests normally
pnpm run test:integration
pnpm playwright test
# Open UI to run tests
pnpm run test:integration:debug
pnpm playwright test --ui
# Run tests in a specific file only
pnpm run test:integration -- integration-test/file-name-here.spec.ts
pnpm run test:integration:debug -- integration-test/file-name-here.spec.ts
pnpm playwright test integration-test/dashboard/file-name-here.spec.ts
# Compile the entire app before running the tests.
# DOES NOT hot reload the tests.
# Prefer not using this when you are trying to fix a test;
# prefer using this when you just want to know which tests are failing (if any).
PROD=1 pnpm run test:integration
PROD=1 pnpm run test:integration:debug
PROD=1 pnpm run test:integration -- integration-test/file-name-here.spec.ts
PROD=1 pnpm run test:integration:debug -- integration-test/file-name-here.spec.ts
PROD=true pnpm playwright test
```

## Getting started

```ts
test.test('test name here', ({ page }) =>
actions.mockAllAndLogin({ page }).then(
// ONLY chain methods from `pageActions`.
// Using methods not in `pageActions` is UNDEFINED BEHAVIOR.
// If it is absolutely necessary though, please remember to `await` the method chain.
// Note that the `async`/`await` pair is REQUIRED, as `Actions` subclasses are `PromiseLike`s,
// not `Promise`s, which causes Playwright to output a type error.
async ({ pageActions }) => await pageActions.goTo.drive(),
),
)
// ONLY chain methods from `pageActions`.
// Using methods not in `pageActions` is UNDEFINED BEHAVIOR.
// If it is absolutely necessary though, please remember to `await` the method chain.
test('test name here', ({ page }) => mockAllAndLogin({ page }).goToPage.drive())
```

### Perform arbitrary actions (e.g. actions on the API)

```ts
test.test('test name here', ({ page }) =>
actions.mockAllAndLogin({ page }).then(
async ({ pageActions, api }) =>
await pageActions.do(() => {
api.foo()
api.bar()
test.expect(api.baz()?.quux).toEqual('bar')
}),
),
)
test('test name here', ({ page }) =>
mockAllAndLogin({ page }).do((_page, { api }) => {
api.foo()
api.bar()
expect(api.baz()?.quux).toEqual('bar')
}))
```
4 changes: 2 additions & 2 deletions app/gui/integration-test/dashboard/actions/BaseActions.ts
Original file line number Diff line number Diff line change
@@ -18,8 +18,8 @@ export interface PageCallback<Context> {
}

/** A callback that performs actions on a {@link Locator}. */
export interface LocatorCallback {
(input: Locator): Promise<void> | void
export interface LocatorCallback<Context> {
(input: Locator, context: Context): Promise<void> | void
}

export interface BaseActionsClass<Context, Args extends readonly unknown[] = []> {
36 changes: 19 additions & 17 deletions app/gui/integration-test/dashboard/actions/DrivePageActions.ts
Original file line number Diff line number Diff line change
@@ -124,9 +124,9 @@ export default class DrivePageActions<Context> extends PageActions<Context> {
}

/** Interact with the assets search bar. */
withSearchBar(callback: LocatorCallback) {
return this.step('Interact with search bar', (page) =>
callback(page.getByTestId('asset-search-bar').getByPlaceholder(/(?:)/)),
withSearchBar(callback: LocatorCallback<Context>) {
return this.step('Interact with search bar', (page, context) =>
callback(page.getByTestId('asset-search-bar').getByPlaceholder(/(?:)/), context),
)
}

@@ -152,9 +152,9 @@ export default class DrivePageActions<Context> extends PageActions<Context> {
)
},
/** Interact with the column heading for the "name" column. */
withNameColumnHeading(callback: LocatorCallback) {
return self.step('Interact with "name" column heading', (page) =>
callback(locateNameColumnHeading(page)),
withNameColumnHeading(callback: LocatorCallback<Context>) {
return self.step('Interact with "name" column heading', (page, context) =>
callback(locateNameColumnHeading(page), context),
)
},
/** Click the column heading for the "modified" column to change its sort order. */
@@ -164,9 +164,9 @@ export default class DrivePageActions<Context> extends PageActions<Context> {
)
},
/** Interact with the column heading for the "modified" column. */
withModifiedColumnHeading(callback: LocatorCallback) {
return self.step('Interact with "modified" column heading', (page) =>
callback(locateModifiedColumnHeading(page)),
withModifiedColumnHeading(callback: LocatorCallback<Context>) {
return self.step('Interact with "modified" column heading', (page, context) =>
callback(locateModifiedColumnHeading(page), context),
)
},
/** Click to select a specific row. */
@@ -319,8 +319,10 @@ export default class DrivePageActions<Context> extends PageActions<Context> {
}

/** Interact with the drive view (the main container of this page). */
withDriveView(callback: LocatorCallback) {
return this.step('Interact with drive view', (page) => callback(locateDriveView(page)))
withDriveView(callback: LocatorCallback<Context>) {
return this.step('Interact with drive view', (page, context) =>
callback(locateDriveView(page), context),
)
}

/** Create a new folder using the icon in the Drive Bar. */
@@ -432,9 +434,9 @@ export default class DrivePageActions<Context> extends PageActions<Context> {
}

/** Interact with the Asset Panel. */
withAssetPanel(callback: LocatorCallback) {
return this.step('Interact with asset panel', async (page) => {
await callback(locateAssetPanel(page))
withAssetPanel(callback: LocatorCallback<Context>) {
return this.step('Interact with asset panel', async (page, context) => {
await callback(locateAssetPanel(page), context)
})
}

@@ -446,9 +448,9 @@ export default class DrivePageActions<Context> extends PageActions<Context> {
}

/** Interact with the context menus (the context menus MUST be visible). */
withContextMenus(callback: LocatorCallback) {
return this.step('Interact with context menus', async (page) => {
await callback(locateContextMenu(page))
withContextMenus(callback: LocatorCallback<Context>) {
return this.step('Interact with context menus', async (page, context) => {
await callback(locateContextMenu(page), context)
})
}
}
Original file line number Diff line number Diff line change
@@ -32,9 +32,9 @@ export default class ForgotPasswordPageActions<Context> extends BaseActions<Cont
}

/** Interact with the email input. */
withEmailInput(callback: LocatorCallback) {
return this.step('Interact with email input', async (page) => {
await callback(page.getByPlaceholder(TEXT.emailPlaceholder))
withEmailInput(callback: LocatorCallback<Context>) {
return this.step('Interact with email input', async (page, context) => {
await callback(page.getByPlaceholder(TEXT.emailPlaceholder), context)
})
}

Original file line number Diff line number Diff line change
@@ -79,10 +79,10 @@ export default class LoginPageActions<Context> extends BaseActions<Context> {
}

/** Interact with the email input. */
withEmailInput(callback: LocatorCallback) {
return this.step('Interact with email input', async (page) => {
await callback(page.getByPlaceholder(TEXT.emailPlaceholder))
})
withEmailInput(callback: LocatorCallback<Context>) {
return this.step('Interact with email input', (page, context) =>
callback(page.getByPlaceholder(TEXT.emailPlaceholder), context),
)
}

/** Internal login logic shared between all public methods. */
Original file line number Diff line number Diff line change
@@ -20,10 +20,10 @@ export default class NewDataLinkModalActions<Context> extends BaseActions<Contex
}

/** Interact with the "name" input - for example, to set the name using `.fill("")`. */
withNameInput(callback: LocatorCallback) {
return this.step('Interact with "name" input', async (page) => {
withNameInput(callback: LocatorCallback<Context>) {
return this.step('Interact with "name" input', async (page, context) => {
const locator = locateNewDataLinkModal(page).getByPlaceholder(TEXT.datalinkNamePlaceholder)
await callback(locator)
await callback(locator, context)
})
}
}
Original file line number Diff line number Diff line change
@@ -68,9 +68,9 @@ export default class RegisterPageActions<Context> extends BaseActions<Context> {
}

/** Interact with the email input. */
withEmailInput(callback: LocatorCallback) {
return this.step('Interact with email input', async (page) => {
await callback(page.getByPlaceholder(TEXT.emailPlaceholder))
withEmailInput(callback: LocatorCallback<Context>) {
return this.step('Interact with email input', async (page, context) => {
await callback(page.getByPlaceholder(TEXT.emailPlaceholder), context)
})
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @file Actions for the "account" form in settings. */
import { TEXT } from '.'
import type { LocatorCallback } from './BaseActions'
import type PageActions from './PageActions'
import SettingsAccountTabActions from './SettingsAccountTabActions'
import SettingsFormActions from './SettingsFormActions'
@@ -28,4 +29,14 @@ export default class SettingsAccountFormActions<Context> extends SettingsFormAct
this.locate(page).getByLabel(TEXT.userNameSettingsInput).getByRole('textbox').fill(name),
)
}

/** Interact with the "name" input of this form. */
withName(callback: LocatorCallback<Context>) {
return this.step("Interact with 'name' input of 'organization' form", (page, context) =>
callback(
this.locate(page).getByLabel(TEXT.organizationNameSettingsInput).getByRole('textbox'),
context,
),
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @file Actions for the "organization" form in settings. */
import { TEXT } from '.'
import type { LocatorCallback } from './BaseActions'
import type PageActions from './PageActions'
import SettingsFormActions from './SettingsFormActions'
import SettingsOrganizationTabActions from './SettingsOrganizationTabActions'
@@ -32,6 +33,16 @@ export default class SettingsOrganizationFormActions<Context> extends SettingsFo
)
}

/** Interact with the "name" input of this form. */
withName(callback: LocatorCallback<Context>) {
return this.step("Interact with 'name' input of 'organization' form", (page, context) =>
callback(
this.locate(page).getByLabel(TEXT.organizationNameSettingsInput).getByRole('textbox'),
context,
),
)
}

/** Fill the "email" input of this form. */
fillEmail(name: string) {
return this.step("Fill 'email' input of 'organization' form", (page) =>
@@ -42,6 +53,16 @@ export default class SettingsOrganizationFormActions<Context> extends SettingsFo
)
}

/** Interact with the "email" input of this form. */
withEmail(callback: LocatorCallback<Context>) {
return this.step("Interact with 'email' input of 'organization' form", (page, context) =>
callback(
this.locate(page).getByLabel(TEXT.organizationEmailSettingsInput).getByRole('textbox'),
context,
),
)
}

/** Fill the "website" input of this form. */
fillWebsite(name: string) {
return this.step("Fill 'website' input of 'organization' form", (page) =>
@@ -52,6 +73,16 @@ export default class SettingsOrganizationFormActions<Context> extends SettingsFo
)
}

/** Interact with the "website" input of this form. */
withWebsite(callback: LocatorCallback<Context>) {
return this.step("Interact with 'website' input of 'organization' form", (page, context) =>
callback(
this.locate(page).getByLabel(TEXT.organizationWebsiteSettingsInput).getByRole('textbox'),
context,
),
)
}

/** Fill the "location" input of this form. */
fillLocation(name: string) {
return this.step("Fill 'location' input of 'organization' form", (page) =>
@@ -61,4 +92,14 @@ export default class SettingsOrganizationFormActions<Context> extends SettingsFo
.fill(name),
)
}

/** Interact with the "location" input of this form. */
withLocation(callback: LocatorCallback<Context>) {
return this.step("Interact with 'name' input of 'organization' form", (page, context) =>
callback(
this.locate(page).getByLabel(TEXT.organizationLocationSettingsInput).getByRole('textbox'),
context,
),
)
}
}
Original file line number Diff line number Diff line change
@@ -53,9 +53,9 @@ export default class StartModalActions<Context> extends BaseActions<Context> {
}

/** Interact with the "start" modal. */
withStartModal(callback: LocatorCallback) {
return this.step('Interact with start modal', async (page) => {
await callback(this.locateStartModal(page))
withStartModal(callback: LocatorCallback<Context>) {
return this.step('Interact with start modal', async (page, context) => {
await callback(this.locateStartModal(page), context)
})
}
}
6 changes: 3 additions & 3 deletions app/gui/integration-test/dashboard/actions/index.ts
Original file line number Diff line number Diff line change
@@ -61,14 +61,14 @@ async function login({ page }: MockParams, email = '[email protected]', password
async function waitForLoaded(page: Page) {
await page.waitForLoadState()

await test.expect(page.getByTestId('spinner')).toHaveCount(0)
await test.expect(page.getByTestId('loading-app-message')).not.toBeVisible({ timeout: 30_000 })
await expect(page.getByTestId('spinner')).toHaveCount(0)
await expect(page.getByTestId('loading-app-message')).not.toBeVisible({ timeout: 30_000 })
}

/** Wait for the dashboard to load. */
async function waitForDashboardToLoad(page: Page) {
await waitForLoaded(page)
await test.expect(page.getByTestId('after-auth-layout')).toBeAttached()
await expect(page.getByTestId('after-auth-layout')).toBeAttached()
}

/** A placeholder date for visual regression testing. */
2 changes: 1 addition & 1 deletion app/gui/integration-test/dashboard/assetPanel.spec.ts
Original file line number Diff line number Diff line change
@@ -68,7 +68,7 @@ test('asset panel contents', ({ page }) =>
.driveTable.clickRow(0)
.toggleDescriptionAssetPanel()
.do(async () => {
await test.expect(locateAssetPanelDescription(page)).toHaveText(DESCRIPTION)
await expect(locateAssetPanelDescription(page)).toHaveText(DESCRIPTION)
// `getByText` is required so that this assertion works if there are multiple permissions.
// This is not visible; "Shared with" should only be visible on the Enterprise plan.
// await expect(locateAssetPanelPermissions(page).getByText(USERNAME)).toBeVisible()
Loading