Example electron test (#3408)

* example test mostly working

* add electron test to CI
This commit is contained in:
Kurt Hutten
2024-08-13 17:00:56 +10:00
committed by GitHub
parent d9feb92d70
commit 128c9cb3f8
5 changed files with 282 additions and 43 deletions

View File

@ -171,7 +171,7 @@ jobs:
if [[ ! -f "test-results/.last-run.json" ]]; then if [[ ! -f "test-results/.last-run.json" ]]; then
# if no last run artifact, than run plawright normally # if no last run artifact, than run plawright normally
echo "run playwright normally" echo "run playwright normally"
yarn playwright test --project="Google Chrome" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true yarn playwright test --project="Google Chrome" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert='@(snapshot|electron)' || true
# # send to axiom # # send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
fi fi
@ -186,7 +186,7 @@ jobs:
if [[ $failed_tests -gt 0 ]]; then if [[ $failed_tests -gt 0 ]]; then
echo "retried=true" >>$GITHUB_OUTPUT echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry" echo "run playwright with last failed tests and retry $retry"
yarn playwright test --project="Google Chrome" --last-failed --grep-invert=@snapshot || true yarn playwright test --project="Google Chrome" --last-failed --grep-invert='@(snapshot|electron)' || true
# send to axiom # send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
retry=$((retry + 1)) retry=$((retry + 1))
@ -233,6 +233,159 @@ jobs:
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
playwright-electron:
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1]
shardTotal: [1]
needs: check-rust-changes
steps:
- name: Tune GitHub-hosted runner network
uses: smorimoto/tune-github-hosted-runner-network@v1
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- uses: KittyCAD/action-install-cli@main
- name: Install dependencies
run: yarn
- name: Cache Playwright Browsers
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
- name: Install Playwright Browsers
run: yarn playwright install chromium --with-deps
# run: yarn playwright install --with-deps
- name: Download Wasm Cache
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v6
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: src/wasm-lib/pkg
- name: copy wasm blob
if: needs.check-rust-changes.outputs.rust-changed == 'false'
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
continue-on-error: true
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Wasm (because rust diff)
if: needs.check-rust-changes.outputs.rust-changed == 'true'
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: OR Cache Wasm (because wasm cache failed)
if: steps.download-wasm.outcome == 'failure'
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Install vector
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
chmod +x /tmp/vector.sh
/tmp/vector.sh -y -no-modify-path
mkdir -p /tmp/vector
cp .github/workflows/vector.toml /tmp/vector.toml
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
cat /tmp/vector.toml
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
- name: Build Wasm (because rust diff)
if: needs.check-rust-changes.outputs.rust-changed == 'true'
run: yarn build:wasm
- name: OR Build Wasm (because wasm cache failed)
if: steps.download-wasm.outcome == 'failure'
run: yarn build:wasm
- name: build web
run: yarn build:local
- uses: actions/download-artifact@v4
if: always()
continue-on-error: true
with:
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
- name: Run ubuntu/chrome flow (with retries)
id: retry
if: always()
run: |
if [[ ! -f "test-results/.last-run.json" ]]; then
# if no last run artifact, than run plawright normally
echo "run playwright normally"
yarn playwright test --project="Google Chrome" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep=@electron || true
# # send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
fi
retry=1
max_retrys=4
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
while [[ $retry -le $max_retrys ]]; do
if [[ -f "test-results/.last-run.json" ]]; then
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
if [[ $failed_tests -gt 0 ]]; then
echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry"
yarn playwright test --project="Google Chrome" --last-failed --grep=@electron || true
# send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
retry=$((retry + 1))
else
echo "retried=false" >>$GITHUB_OUTPUT
exit 0
fi
else
echo "retried=false" >>$GITHUB_OUTPUT
exit 0
fi
done
echo "retried=false" >>$GITHUB_OUTPUT
if [[ -f "test-results/.last-run.json" ]]; then
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
if [[ $failed_tests -gt 0 ]]; then
# if it still fails after 3 retrys, then fail the job
exit 1
fi
fi
exit 0
env:
CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: send to axiom
if: always()
shell: bash
run: |
node playwrightProcess.mjs | tee /tmp/github-actions.log
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-electron-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
retention-days: 30
overwrite: true
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-electron-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
retention-days: 30
overwrite: true
playwright-macos: playwright-macos:
timeout-minutes: 30 timeout-minutes: 30
runs-on: macos-14 runs-on: macos-14
@ -325,7 +478,7 @@ jobs:
if [[ ! -f "test-results/.last-run.json" ]]; then if [[ ! -f "test-results/.last-run.json" ]]; then
# if no last run artifact, than run plawright normally # if no last run artifact, than run plawright normally
echo "run playwright normally" echo "run playwright normally"
yarn playwright test --project="webkit" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true yarn playwright test --project="webkit" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert='@(snapshot|electron)' || true
# # send to axiom # # send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
fi fi
@ -340,7 +493,7 @@ jobs:
if [[ $failed_tests -gt 0 ]]; then if [[ $failed_tests -gt 0 ]]; then
echo "retried=true" >>$GITHUB_OUTPUT echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry" echo "run playwright with last failed tests and retry $retry"
yarn playwright test --project="webkit" --last-failed --grep-invert=@snapshot || true yarn playwright test --project="webkit" --last-failed --grep-invert='@(snapshot|electron)' || true
# send to axiom # send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
retry=$((retry + 1)) retry=$((retry + 1))

5
.gitignore vendored
View File

@ -65,4 +65,7 @@ venv
.vite/ .vite/
# electron # electron
out/ out/
src-tauri/target
electron-test-projects-dir

View File

@ -1,40 +1,102 @@
import { _electron as electron, test } from '@playwright/test'; import { _electron as electron, test, expect } from '@playwright/test'
import { import {
getUtils, getUtils,
TEST_COLORS,
setup,
tearDown, tearDown,
commonPoints,
PERSIST_MODELING_CONTEXT,
} from './test-utils' } from './test-utils'
import fs from 'node:fs'
import { secrets } from './secrets'
test.describe("when a project", async () => { test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
// This was the very test created. It provides the foundation for the })
// rest of the tests we have to write.
test('is created', async ({ page }) => { test('When the project folder is empty, user can create new project and open it.', { tag: '@electron' }, async () => {
// Launch Electron app. // create or otherwise clear the folder ./electron-test-projects-dir
const electronApp = await electron.launch({ args: ['.'] }) const fileName = './electron-test-projects-dir'
try {
// Evaluation expression in the Electron context. fs.rmdirSync(fileName, { recursive: true })
const appPath = await electronApp.evaluate(async ({ app }) => { } catch (e) {
// This runs in the main Electron process, parameter here is always console.error(e)
// the result of the require('electron') in the main app script. }
return app.getAppPath();
}); fs.mkdirSync(fileName)
console.log(appPath);
// get full path for ./electron-test-projects-dir
// Get the first window that the app opens, wait if necessary. const fullPath = fs.realpathSync(fileName)
const window = await electronApp.firstWindow();
// Print the title. const electronApp = await electron.launch({
console.log(await window.title()); args: ['.'],
// Capture a screenshot. })
await window.screenshot({ path: 'intro.png' });
// Direct Electron console to Node terminal. await electronApp.evaluate(async ({ app }) => {
window.on('console', console.log); return app.getAppPath()
// Click button. })
await window.click('text=Click me');
// Exit app. const page = await electronApp.firstWindow()
await electronApp.close();
}) // Set local storage directly using evaluate
await page.evaluate(
(token) => localStorage.setItem('TOKEN_PERSIST_KEY', token),
secrets.token
)
await page.evaluate((fullPath) =>
localStorage.setItem(
'APP_SETTINGS_OVERRIDE',
JSON.stringify({
projectDirectory: fullPath,
})
), fullPath
)
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// expect to see text "No Projects found"
await expect(page.getByText('No Projects found')).toBeVisible()
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText('project-000')).toBeVisible()
await page.getByText('project-000').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeEnabled({
timeout: 20_000,
})
await page.locator('.cm-content').fill(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([-87.4, 282.92], %)
|> line([324.07, 27.199], %, $seg01)
|> line([118.328, -291.754], %)
|> line([-180.04, -202.08], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(200, sketch001)`)
const pointOnModel = { x: 660, y: 250 }
// check the model loaded by checking it's grey
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [132, 132, 132]), {
timeout: 10_000,
})
.toBeLessThan(10)
await page.mouse.click(pointOnModel.x, pointOnModel.y)
// check user can interact with model by checking it turns yellow
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [176, 180, 132]))
.toBeLessThan(10)
await electronApp.close()
}) })

View File

@ -441,6 +441,15 @@ export const readAppSettingsFile = async () => {
} }
const configToml = await window.electron.readFile(settingsPath) const configToml = await window.electron.readFile(settingsPath)
const configObj = parseAppSettings(configToml) const configObj = parseAppSettings(configToml)
const overrideJSON = localStorage.getItem('APP_SETTINGS_OVERRIDE')
if (overrideJSON) {
try {
const override = JSON.parse(overrideJSON)
configObj.app = { ...configObj.app, ...override }
} catch (e) {
console.error('Error parsing APP_SETTINGS_OVERRIDE:', e)
}
}
return configObj return configObj
} }

View File

@ -2,7 +2,15 @@
// template that ElectronJS provides. // template that ElectronJS provides.
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration' import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { app, BrowserWindow, ipcMain, dialog, shell, protocol, net } from 'electron' import {
app,
BrowserWindow,
ipcMain,
dialog,
shell,
protocol,
net,
} from 'electron'
import path from 'path' import path from 'path'
import url from 'url' import url from 'url'
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
@ -41,7 +49,7 @@ const createWindow = () => {
// Open the DevTools. // Open the DevTools.
// mainWindow.webContents.openDevTools() // mainWindow.webContents.openDevTools()
mainWindow.show() mainWindow.show()
} }
// Quit when all windows are closed, except on macOS. There, it's common // Quit when all windows are closed, except on macOS. There, it's common
@ -153,8 +161,12 @@ app.whenReady().then(() => {
const maybeAbsolutePath = path.join(__dirname, filePath) const maybeAbsolutePath = path.join(__dirname, filePath)
const bypassCustomProtocolHandlers = true const bypassCustomProtocolHandlers = true
if (fss.existsSync(maybeAbsolutePath)) { if (fss.existsSync(maybeAbsolutePath)) {
console.log(`Intercepted local-asbolute path ${filePath}, rebuilt it as ${maybeAbsolutePath}`) console.log(
return net.fetch(url.pathToFileURL(maybeAbsolutePath).toString(), { bypassCustomProtocolHandlers }) `Intercepted local-asbolute path ${filePath}, rebuilt it as ${maybeAbsolutePath}`
)
return net.fetch(url.pathToFileURL(maybeAbsolutePath).toString(), {
bypassCustomProtocolHandlers,
})
} }
console.log(`Default fetch to ${filePath}`) console.log(`Default fetch to ${filePath}`)
return net.fetch(request.url, { bypassCustomProtocolHandlers }) return net.fetch(request.url, { bypassCustomProtocolHandlers })