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 no last run artifact, than run plawright 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
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
fi
@ -186,7 +186,7 @@ jobs:
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-invert=@snapshot || true
yarn playwright test --project="Google Chrome" --last-failed --grep-invert='@(snapshot|electron)' || true
# send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
retry=$((retry + 1))
@ -233,6 +233,159 @@ jobs:
retention-days: 30
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:
timeout-minutes: 30
runs-on: macos-14
@ -325,7 +478,7 @@ jobs:
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="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
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
fi
@ -340,7 +493,7 @@ jobs:
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="webkit" --last-failed --grep-invert=@snapshot || true
yarn playwright test --project="webkit" --last-failed --grep-invert='@(snapshot|electron)' || true
# send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
retry=$((retry + 1))

3
.gitignore vendored
View File

@ -66,3 +66,6 @@ venv
# electron
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 {
getUtils,
TEST_COLORS,
setup,
tearDown,
commonPoints,
PERSIST_MODELING_CONTEXT,
} from './test-utils'
import fs from 'node:fs'
import { secrets } from './secrets'
test.describe("when a project", async () => {
// 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 }) => {
// Launch Electron app.
const electronApp = await electron.launch({ args: ['.'] })
// Evaluation expression in the Electron context.
const appPath = await electronApp.evaluate(async ({ app }) => {
// This runs in the main Electron process, parameter here is always
// the result of the require('electron') in the main app script.
return app.getAppPath();
});
console.log(appPath);
// Get the first window that the app opens, wait if necessary.
const window = await electronApp.firstWindow();
// Print the title.
console.log(await window.title());
// Capture a screenshot.
await window.screenshot({ path: 'intro.png' });
// Direct Electron console to Node terminal.
window.on('console', console.log);
// Click button.
await window.click('text=Click me');
// Exit app.
await electronApp.close();
})
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test('When the project folder is empty, user can create new project and open it.', { tag: '@electron' }, async () => {
// create or otherwise clear the folder ./electron-test-projects-dir
const fileName = './electron-test-projects-dir'
try {
fs.rmdirSync(fileName, { recursive: true })
} catch (e) {
console.error(e)
}
fs.mkdirSync(fileName)
// get full path for ./electron-test-projects-dir
const fullPath = fs.realpathSync(fileName)
const electronApp = await electron.launch({
args: ['.'],
})
await electronApp.evaluate(async ({ app }) => {
return app.getAppPath()
})
const page = await electronApp.firstWindow()
// 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 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
}

View File

@ -2,7 +2,15 @@
// template that ElectronJS provides.
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 url from 'url'
import fs from 'node:fs/promises'
@ -153,8 +161,12 @@ app.whenReady().then(() => {
const maybeAbsolutePath = path.join(__dirname, filePath)
const bypassCustomProtocolHandlers = true
if (fss.existsSync(maybeAbsolutePath)) {
console.log(`Intercepted local-asbolute path ${filePath}, rebuilt it as ${maybeAbsolutePath}`)
return net.fetch(url.pathToFileURL(maybeAbsolutePath).toString(), { bypassCustomProtocolHandlers })
console.log(
`Intercepted local-asbolute path ${filePath}, rebuilt it as ${maybeAbsolutePath}`
)
return net.fetch(url.pathToFileURL(maybeAbsolutePath).toString(), {
bypassCustomProtocolHandlers,
})
}
console.log(`Default fetch to ${filePath}`)
return net.fetch(request.url, { bypassCustomProtocolHandlers })