Skip to content

Commit

Permalink
Integration Tests (#11186)
Browse files Browse the repository at this point in the history
A stub for integration tests to be run locally; part of #8487

To run tests, you need to:
1. Build IDE package: `./run ide build`
2. set ENSO_TEST_USER and ENSO_TEST_USER_PASSWORD to some working credentials (I used my personal account, but there will be also test user soon)
3. run `corepack pnpm -r --filter enso exec playwright test`

The tests are run with a separate projects directory set up in tmpdir, so any local workspace dir is not affected.

The only test so far just checks if it's possible to log in and create a new project.
  • Loading branch information
farmaazon authored Sep 27, 2024
1 parent c82e075 commit e6b904d
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 19 deletions.
4 changes: 3 additions & 1 deletion app/ide-desktop/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@types/semver": "^7.5.8",
"@types/tar": "^6.1.4",
"@types/yargs": "^17.0.30",
"@playwright/test": "^1.45.0",
"cross-env": "^7.0.3",
"electron": "31.2.0",
"electron-builder": "^24.13.3",
Expand All @@ -50,7 +51,8 @@
"fast-glob": "^3.2.12",
"portfinder": "^1.0.32",
"tsx": "^4.7.1",
"vite": "^5.3.5"
"vite": "^5.3.5",
"playwright": "^1.45.0"
},
"//": [
"vite is required for the watch script",
Expand Down
20 changes: 20 additions & 0 deletions app/ide-desktop/client/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/** @file Playwright browser testing configuration. */
import { defineConfig } from '@playwright/test'

/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/strict-boolean-expressions */

export default defineConfig({
testDir: './tests',
forbidOnly: !!process.env.CI,
workers: 1,
timeout: 60000,
reportSlowTests: { max: 5, threshold: 60000 },
globalSetup: './tests/setup.ts',
expect: {
timeout: 5000,
toHaveScreenshot: { threshold: 0 },
},
use: {
actionTimeout: 5000,
},
})
16 changes: 7 additions & 9 deletions app/ide-desktop/client/src/fileAssociations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,13 @@ function getClientArguments(args = process.argv): readonly string[] {
return args.slice(separatorIndex + 1)
}
} else {
const noSandbox = args.indexOf('--no-sandbox')
if (noSandbox !== NOT_FOUND) {
let v = [...args]
v.splice(noSandbox, 1)
return v.slice(1)
} else {
// Drop the leading executable name.
return args.slice(1)
}
// Drop the leading executable name and known electron options.
return (
args
.slice(1)
// Omitting $ in --inspect and --remote-debugging-port is intentional.
.filter(option => !/^--no-sandbox$|^--inspect|^--remote-debugging-port/.test(option))
)
}
}

Expand Down
10 changes: 5 additions & 5 deletions app/ide-desktop/client/src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,12 @@ declare global {
// @ts-expect-error The index signature is intentional to disallow unknown env vars.
readonly ENSO_SUPPORTS_VIBRANCY?: string

// === E2E test variables ===
// === Integration test variables ===

// @ts-expect-error The index signature is intentional to disallow unknown env vars.
readonly IS_IN_PLAYWRIGHT_TEST?: `${boolean}`
// @ts-expect-error The index signature is intentional to disallow unknown env vars.
readonly PWDEBUG?: '1'
readonly ENSO_TEST?: string
readonly ENSO_TEST_USER?: string
readonly ENSO_TEST_USER_PASSWORD?: string
ENSO_TEST_EXEC_PATH?: string

// === Electron watch script variables ===

Expand Down
3 changes: 3 additions & 0 deletions app/ide-desktop/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ class App {
enableBlinkFeatures: argGroups.chrome.options.enableBlinkFeatures.value,
disableBlinkFeatures: argGroups.chrome.options.disableBlinkFeatures.value,
spellcheck: false,
...(process.env.ENSO_TEST != null && process.env.ENSO_TEST !== '' ?
{ partition: 'test' }
: {}),
}
const windowPreferences: electron.BrowserWindowConstructorOptions = {
webPreferences,
Expand Down
12 changes: 8 additions & 4 deletions app/ide-desktop/client/src/projectManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,15 @@ export function getProjectRoot(subtreePath: string): string | null {

/** Get the directory that stores Enso projects. */
export function getProjectsDirectory(): string {
const documentsPath = desktopEnvironment.DOCUMENTS
if (documentsPath === undefined) {
return pathModule.join(os.homedir(), 'enso', 'projects')
if (process.env.ENSO_TEST != null && process.env.ENSO_TEST !== '') {
return pathModule.join(os.tmpdir(), 'enso-test-projects', process.env.ENSO_TEST)
} else {
return pathModule.join(documentsPath, 'enso-projects')
const documentsPath = desktopEnvironment.DOCUMENTS
if (documentsPath === undefined) {
return pathModule.join(os.homedir(), 'enso', 'projects')
} else {
return pathModule.join(documentsPath, 'enso-projects')
}
}
}

Expand Down
13 changes: 13 additions & 0 deletions app/ide-desktop/client/tests/createNewProject.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** @file A test for basic flow of the application: open project and see if nodes appear. */

/* eslint-disable @typescript-eslint/no-magic-numbers */

import { expect } from '@playwright/test'
import { electronTest, loginAsTestUser } from './electronTest'

electronTest('Create new project', async page => {
await loginAsTestUser(page)
await expect(page.getByRole('button', { name: 'New Project', exact: true })).toBeVisible()
await page.getByRole('button', { name: 'New Project', exact: true }).click()
await expect(page.locator('.GraphNode'), {}).toBeVisible({ timeout: 30000 })
})
47 changes: 47 additions & 0 deletions app/ide-desktop/client/tests/electronTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/** @file Commonly used functions for electron tests */

import { _electron, expect, type Page, test } from '@playwright/test'

/**
* Tests run on electron executable.
*
* Similar to playwright's test, but launches electron, and passes Page of the main window.
*/
export function electronTest(name: string, body: (page: Page) => Promise<void> | void) {
test(name, async () => {
const app = await _electron.launch({
executablePath: process.env.ENSO_TEST_EXEC_PATH ?? '',
env: { ...process.env, ['ENSO_TEST']: name },
})
const page = await app.firstWindow()
await body(page)
await app.close()
})
}

/**
* Login as test user. This function asserts that page is the login page, and uses
* credentials from ENSO_TEST_USER and ENSO_TEST_USER_PASSWORD env variables.
*/
export async function loginAsTestUser(page: Page) {
// Login screen
await expect(page.getByRole('textbox', { name: 'email' })).toBeVisible()
await expect(page.getByRole('textbox', { name: 'password' })).toBeVisible()
if (process.env.ENSO_TEST_USER == null || process.env.ENSO_TEST_USER_PASSWORD == null) {
throw Error(
'Cannot log in; `ENSO_TEST_USER` and `ENSO_TEST_USER_PASSWORD` env variables are not provided',
)
}
await page.keyboard.insertText(process.env.ENSO_TEST_USER)
await page.keyboard.press('Tab')
await page.keyboard.insertText(process.env.ENSO_TEST_USER_PASSWORD)
await page.keyboard.press('Enter')

// Accept terms screen
await expect(page.getByText('I agree')).toHaveCount(2)
await expect(page.getByRole('button')).toHaveCount(1)
for (const checkbox of await page.getByText('I agree').all()) {
await checkbox.click()
}
await page.getByRole('button').click()
}
30 changes: 30 additions & 0 deletions app/ide-desktop/client/tests/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/** @file {@link setup} function for all tests. */

import * as fs from 'node:fs'

const POSSIBLE_EXEC_PATHS = [
'../../../dist/ide/linux-unpacked/enso',
'../../../dist/ide/win-unpacked/Enso.exe',
'../../../dist/ide/mac/Enso.app/Contents/MacOS/Enso',
'../../../dist/ide/mac-arm64/Enso.app/Contents/MacOS/Enso',
]

/**
* Setup for all tests: checks if and where electron exec is.
* @throws when no Enso package could be found.
*/
export default function setup() {
const execPath = POSSIBLE_EXEC_PATHS.find(path => {
try {
fs.accessSync(path, fs.constants.X_OK)
return true
} catch (err) {
return false
}
})
if (execPath != null) {
process.env.ENSO_TEST_EXEC_PATH = execPath
} else {
throw Error('Cannot find Enso package')
}
}
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e6b904d

Please sign in to comment.