Skip to content

Commit

Permalink
Merge pull request #444 from ynput/432-Testing-Setup-Playwright-end-t…
Browse files Browse the repository at this point in the history
…o-end-testing-and-write-first-tests

Testing: setup playwright and first basic end to end tests
  • Loading branch information
flynput authored Aug 13, 2024
2 parents 0dfbfac + 808c2d1 commit a5aa069
Show file tree
Hide file tree
Showing 19 changed files with 519 additions and 3 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ yarn.lock
yarn-error.log
.env.local
.env
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

playwright/.auth
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
"upgrade-arc": "yarn upgrade @ynput/ayon-react-components@latest",
"generate-token": "node --env-file=.env.local ./gen/getAuth.js",
"generate-rest": "npx @rtk-query/codegen-openapi ./gen/openapi-config.ts",
"generate-gql": "cd gen && graphql-codegen --require dotenv/config"
"generate-gql": "cd gen && graphql-codegen --require dotenv/config",
"test": "npx playwright test",
"test-ui": "npx playwright test --ui",
"test-auth": "npx playwright test auth.setup.js",
"test-report": "yarn playwright show-report",
"test-codegen": "npx playwright codegen http://localhost:3000 --load-storage=playwright/.auth/user.json"
},
"dependencies": {
"@dnd-kit/core": "^6.0.8",
Expand Down Expand Up @@ -79,7 +84,8 @@
"@graphql-eslint/eslint-plugin": "^3.20.1",
"@graphql-typed-document-node/core": "^3.2.0",
"@types/lodash": "^4.17.5",
"@types/node": "^20.14.2",
"@playwright/test": "^1.44.0",
"@types/node": "^20.12.12",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/styled-components": "^5.1.34",
Expand All @@ -88,6 +94,7 @@
"@vitejs/plugin-react": "^1.3.0",
"babel-plugin-styled-components": "^2.0.7",
"esbuild-runner": "^2.2.2",
"dotenv": "^16.4.5",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
Expand Down
93 changes: 93 additions & 0 deletions playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// @ts-check
const { defineConfig, devices } = require('@playwright/test')

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
require('dotenv').config({ path: ['.env.local'] })

/**
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
testDir: './tests',
/* Run tests in files in parallel */

fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
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. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.LOCAL_SERVER_URL,

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
// Setup project
{ name: 'setup', testMatch: /.*\.setup\.js/ },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'], // Use prepared auth state.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},

{
name: 'firefox',
use: {
...devices['Desktop Firefox'], // Use prepared auth state.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},

{
name: 'webkit',
use: {
...devices['Desktop Safari'], // Use prepared auth state.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn dev',
url: process.env.LOCAL_SERVER_URL,
reuseExistingServer: !process.env.CI,
},
})
1 change: 1 addition & 0 deletions src/containers/header/AppHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ const Header = () => {
<HeaderButton
className={clsx({ active: menuOpen === 'user' })}
onClick={() => handleToggleMenu('user')}
aria-label='User menu'
ref={userButtonRef}
variant="nav"
style={{ padding: 6 }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const NewProjectDialog = ({ onHide }) => {
<SaveButton
label="Create Project"
onClick={handleSubmit}
active={name && code}
active={name && code && !isOriginalAnatomyLoading && !isSchemaLoading}
saving={isLoading}
disabled={!!(nameValidationError || codeValidationError)}
/>
Expand Down
21 changes: 21 additions & 0 deletions tests/auth.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { test as setup } from '@playwright/test'

const authFile = 'playwright/.auth/user.json'

setup('authenticate', async ({ page }) => {
await page.goto(process.env.LOCAL_SERVER_URL);
console.log('USERNAME:', process.env.NAME)
// Perform authentication steps. Replace these actions with your own.
await page.getByLabel('Username').fill(process.env.NAME)
await page.getByLabel('Password').fill(process.env.PASSWORD)
await page.getByRole('button', { name: 'Login', exact: true }).click()
// Wait until the page receives the cookies.
//
// Sometimes login flow sets cookies in the process of several redirects.
// Wait for the final URL to ensure that the cookies are actually set.
await page.waitForURL('/dashboard/tasks')

// End of authentication steps.

await page.context().storageState({ path: authFile })
})
8 changes: 8 additions & 0 deletions tests/authentication.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { authenticationTest as test} from './fixtures/authenticationPage'

test.use({ storageState: { cookies: [], origins: [] } })

test('login/logout user', async ({authenticationPage, browserName}) => {
await authenticationPage.login(process.env.NAME!, process.env.PASSWORD!)
await authenticationPage.logout()
})
7 changes: 7 additions & 0 deletions tests/bundles.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getBundleName, bundleTest as test} from './fixtures/bundlePage'

test('create/delete bundle', async ({bundlePage, browserName}) => {
const bundleName= getBundleName('test_bundle')(browserName)
await bundlePage.createBundle(bundleName)
await bundlePage.deleteBundle(bundleName)
})
47 changes: 47 additions & 0 deletions tests/fixtures/authenticationPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {expect, test as base } from '@playwright/test'
import type {Page } from '@playwright/test'

class AuthenticationPage {
constructor(
public readonly page: Page,
public readonly browserName: String,
) {}

async goto() {
await this.page.goto('/')
}
async waitForNextPage() {
await this.page.waitForURL('/dashboard/tasks')
}

async login(username: string, password: string) {
await this.goto()

// Perform authentication steps. Replace these actions with your own.
await this.page.getByLabel('Username').fill(username)
await this.page.getByLabel('Password').fill(password)
await this.page.getByRole('button', { name: 'Login', exact: true }).click()
await this.waitForNextPage()
}

async logout() {
// await this.page.getByLabel('Username').fill(process.env.NAME)
// await this.page.getByLabel('Password').fill(process.env.PASSWORD)
// await this.page.getByRole('button', { name: 'Login', exact: true }).click()
// await this.page.waitForURL('/dashboard/tasks')

await this.page.getByLabel('User menu').click()
await this.page.getByText('Sign out').click()
await expect(this.page).toHaveURL('/login')
}
}

const test = base.extend<{ authenticationPage: AuthenticationPage }>({
authenticationPage : async ({ page, browserName }, use) => {
const authenticationPage = new AuthenticationPage(page, browserName);
await use(authenticationPage);
},
});

export default AuthenticationPage
export { test as authenticationTest }
50 changes: 50 additions & 0 deletions tests/fixtures/bundlePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {expect, test as base } from '@playwright/test'
import type {Page } from '@playwright/test'

const getBundleName = prefix => browser => prefix + '_' + browser

class BundlePage {
constructor(
public readonly page: Page,
public readonly browserName: String,
) {}

async goto() {
await this.page.goto('/settings/bundles')
}

async createBundle(name: string) {
await this.goto()
await this.page.goto('/settings/bundles')
await this.page.getByRole('button', { name: 'add Add Bundle' }).click()
await this.page.getByRole('main').getByRole('textbox').fill(name)
await this.page.getByRole('button', { name: 'Select an option...' }).click()
const launcher = '1.0.2'
await this.page.getByText(launcher).click()
await this.page.getByRole('button', { name: 'check Create new bundle' }).click()
await expect(this.page.getByText('Bundle created')).toBeVisible()
await expect(this.page.getByRole('cell', { name })).toBeVisible()

}

async deleteBundle(name) {
await this.goto()
await this.page.getByRole('cell', { name}).click({ button: 'right' })
await this.page.getByRole('menuitem', { name: 'archive Archive' }).click()
await this.page.getByRole('cell', { name: `${name} (archived)` }).click({ button: 'right' })
await this.page.getByRole('menuitem', { name: 'delete Delete' }).click()
await this.page.getByLabel('Delete', { exact: true }).click()
await expect(this.page.getByText('bundles deleted')).toBeVisible()
await expect(this.page.getByRole('cell', { name })).toBeHidden()
}
}

const test = base.extend<{ bundlePage: BundlePage }>({
bundlePage: async ({ page, browserName }, use) => {
const projectPage = new BundlePage(page, browserName);
await use(projectPage);
},
});

export default BundlePage
export { getBundleName, test as bundleTest }
48 changes: 48 additions & 0 deletions tests/fixtures/folderPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { expect, test as base } from '@playwright/test'
import type { Page } from '@playwright/test'

const getFolderName = prefix => browser => prefix + '_' + browser

class FolderPage {
constructor(
public readonly page: Page,
public readonly browserName: String,
) {}

async goto(projectName) {
await this.page.goto(`/projects/${projectName}/editor`)
}

async createFolder(projectName, folderName) {
await this.goto(projectName)
await this.page.getByRole('button', { name: 'create_new_folder Add Folders' }).click()
await expect(this.page.getByText('add new root folder')).toBeVisible()
await this.page.locator('li[data-value="Folder"]').click()
await this.page.locator('input[value="Folder"]').fill(folderName)
await this.page.getByRole('button', { name: 'check Add and Close' }).click()
await expect(this.page.getByRole('cell', { name: folderName })).toBeVisible()
await this.page.getByRole('button', { name: 'check Save Changes' }).click()
await this.page.getByText('Changes saved').click()
}

async deleteFolder(projectName, folderName) {
await this.goto(projectName)
await expect(this.page.getByRole('cell', { name: folderName })).toBeVisible()
await this.page.getByRole('cell', { name: folderName }).click({ button: 'right' })
await this.page.getByRole('menuitem', { name: 'delete Delete' }).click()
await this.page.getByRole('button', { name: 'check Save Changes' }).click()
await this.page.getByText('Changes saved').click()
await expect(this.page.getByRole('cell', { name: folderName })).toBeHidden()
}
}

const test = base.extend<{ folderPage: FolderPage }>({
folderPage: async ({ page, browserName }, use) => {
const folderPage = new FolderPage(page, browserName)
await use(folderPage)
},
})

export default FolderPage

export { getFolderName, test as folderTest }
49 changes: 49 additions & 0 deletions tests/fixtures/projectPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {expect, test as base } from '@playwright/test'
import type {Page } from '@playwright/test'

const getProjectName = prefix => browser => prefix + '_' + browser

class ProjectPage {
constructor(
public readonly page: Page,
public readonly browserName: String,
) {}

async goto() {
await this.page.goto('/manageProjects')
}

async createProject(name: string) {
await this.goto()
await this.page.getByRole('button', { name: 'create_new_folder Add New' }).click()
await this.page.getByPlaceholder('Project Name').fill(name)
await expect(this.page.getByRole('heading', { name: 'Roots' }).nth(1)).toBeVisible()
await this.page.getByRole('button', { name: 'check Create Project' }).click()
await this.page.getByText('Project created').click()
await expect(this.page.getByRole('row', { name: name })).toBeVisible()
}

async deleteProject(name) {
await this.page.goto('/manageProjects')
const projectCell = await this.page.getByRole('cell', { name })
const projectCellText = await this.page.getByText(name, { exact: true })
await projectCell.click({ button: 'right' })
await this.page.getByRole('menuitem', { name: 'archive Deactivate Project' }).click()
await expect(projectCellText).toHaveCSS('font-style', 'italic')
await projectCell.click({ button: 'right' })
await this.page.getByRole('menuitem', { name: 'delete Delete Project' }).click()
await this.page.getByLabel('Delete', { exact: true }).click()
await expect(this.page.getByText(`Project: ${name} deleted`)).toBeVisible()
await expect(this.page.getByRole('cell', { name })).toBeHidden()
}
}

const test = base.extend<{ projectPage: ProjectPage }>({
projectPage: async ({ page, browserName }, use) => {
const projectPage = new ProjectPage(page, browserName);
await use(projectPage);
},
});

export default ProjectPage
export { getProjectName, test as projectTest }
Loading

0 comments on commit a5aa069

Please sign in to comment.