mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-29 14:16:36 +00:00
wip: crude e2e
This commit is contained in:
parent
6a648e9215
commit
e1d9f97d52
58
.github/actions/test/action.yml
vendored
58
.github/actions/test/action.yml
vendored
@ -46,9 +46,56 @@ runs:
|
|||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Setup Virtual Drive on MacOS
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
hdiutil create -size 4096m -layout NONE -o virtual_test_disk.dmg
|
||||||
|
virtual_path=$(hdiutil attach -nomount virtual_test_disk.dmg)
|
||||||
|
echo "TARGET_DRIVE=${virtual_path}" >> $GITHUB_ENV
|
||||||
|
echo "ETCHER_INCLUDE_VIRTUAL_DRIVES=1" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup Virtual Drive on Linux
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
dd if=/dev/zero of=virtual_test_disk.img bs=1M count=4096
|
||||||
|
virtual_path=$(sudo losetup -f --show virtual_test_disk.img)
|
||||||
|
echo "TARGET_DRIVE=${virtual_path}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup Virtual Drive on Windows
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
fsutil file createnew virtual_test_disk.img 4294967296
|
||||||
|
|
||||||
|
# Use DiskPart to attach and list volumes
|
||||||
|
$diskpartScript = @"
|
||||||
|
SELECT VDISK FILE=virtual_test_disk.img
|
||||||
|
ATTACH VDISK
|
||||||
|
LIST VOLUME
|
||||||
|
"@
|
||||||
|
|
||||||
|
diskpart /s $diskpartScript | Out-File -FilePath diskpart_output.txt -Encoding UTF8
|
||||||
|
|
||||||
|
# Extract the volume information from the DiskPart output
|
||||||
|
$diskpartOutput = Get-Content -Path diskpart_output.txt
|
||||||
|
$driveLine = $diskpartOutput | Select-String -Pattern "virtual_test_disk.img"
|
||||||
|
|
||||||
|
# Extract drive letter
|
||||||
|
$driveLetter = $null
|
||||||
|
if ($driveLine) {
|
||||||
|
$tokens = $driveLine -split "\s+"
|
||||||
|
$driveLetter = $tokens[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "TARGET_DRIVE=!drive_letter!:\\" >> %GITHUB_ENV%
|
||||||
|
|
||||||
- name: Test release
|
- name: Test release
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
# Build and Test release
|
||||||
|
|
||||||
## FIXME: causes issues with `xxhash` which tries to load a debug build which doens't exist and cannot be compiled
|
## FIXME: causes issues with `xxhash` which tries to load a debug build which doens't exist and cannot be compiled
|
||||||
# if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then
|
# if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then
|
||||||
# export DEBUG='electron-forge:*,sidecar'
|
# export DEBUG='electron-forge:*,sidecar'
|
||||||
@ -57,8 +104,17 @@ runs:
|
|||||||
npm ci
|
npm ci
|
||||||
npm run lint
|
npm run lint
|
||||||
npm run package
|
npm run package
|
||||||
npm run wdio # test stage, note that it requires the package to be done first
|
|
||||||
|
# tests; note that they required `package` to run before
|
||||||
|
npm run wdio
|
||||||
|
|
||||||
|
# e2e suite requires administrative privileges
|
||||||
|
if [[ '${{ runner.os }}' == 'Windows' ]]; then
|
||||||
|
npm run wdio-e2e
|
||||||
|
else
|
||||||
|
sudo TARGET_DRIVE=${{ env.TARGET_DRIVE }} ETCHER_INCLUDE_VIRTUAL_DRIVES=1 npm run wdio-e2e
|
||||||
|
fi
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# https://www.electronjs.org/docs/latest/api/environment-variables
|
# https://www.electronjs.org/docs/latest/api/environment-variables
|
||||||
ELECTRON_NO_ATTACH_CONSOLE: 'true'
|
ELECTRON_NO_ATTACH_CONSOLE: 'true'
|
||||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -120,4 +120,10 @@ secrets/WINDOWS_SIGNING.pfx
|
|||||||
|
|
||||||
#local development
|
#local development
|
||||||
.yalc
|
.yalc
|
||||||
yalc.lock
|
yalc.lock
|
||||||
|
|
||||||
|
# Test assets
|
||||||
|
virtual_test_disk.dmg
|
||||||
|
virtual_test_disk.img
|
||||||
|
virtual_test_disk.vhd
|
||||||
|
screenshots/
|
@ -419,6 +419,7 @@ export class DriveSelector extends React.Component<
|
|||||||
primary: !showWarnings,
|
primary: !showWarnings,
|
||||||
warning: showWarnings,
|
warning: showWarnings,
|
||||||
disabled: !hasAvailableDrives(),
|
disabled: !hasAvailableDrives(),
|
||||||
|
'data-testid': 'validate-target-button',
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
@ -163,7 +163,7 @@ export function FlashResults({
|
|||||||
/>
|
/>
|
||||||
<Txt>{middleEllipsis(image, 24)}</Txt>
|
<Txt>{middleEllipsis(image, 24)}</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Txt fontSize={24} color="#fff" mb="17px">
|
<Txt data-testid="flash-results" fontSize={24} color="#fff" mb="17px">
|
||||||
{allFailed
|
{allFailed
|
||||||
? i18next.t('flash.flashFailed')
|
? i18next.t('flash.flashFailed')
|
||||||
: i18next.t('flash.flashCompleted')}
|
: i18next.t('flash.flashCompleted')}
|
||||||
|
@ -104,7 +104,9 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Txt color="#fff">{status} </Txt>
|
<Txt data-testid="flash-status" color="#fff">
|
||||||
|
{status}
|
||||||
|
</Txt>
|
||||||
<Txt color={colors[type]}>{position}</Txt>
|
<Txt color={colors[type]}>{position}</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
{type && (
|
{type && (
|
||||||
@ -125,6 +127,7 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
|||||||
warning={warning}
|
warning={warning}
|
||||||
onClick={this.props.callback}
|
onClick={this.props.callback}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
|
data-testid={'flash-now-button'}
|
||||||
style={{
|
style={{
|
||||||
marginTop: 30,
|
marginTop: 30,
|
||||||
}}
|
}}
|
||||||
|
@ -165,6 +165,7 @@ const URLSelector = ({
|
|||||||
cancel={cancel}
|
cancel={cancel}
|
||||||
primaryButtonProps={{
|
primaryButtonProps={{
|
||||||
disabled: loading || !imageURL,
|
disabled: loading || !imageURL,
|
||||||
|
'data-testid': 'source-url-ok-button',
|
||||||
}}
|
}}
|
||||||
action={loading ? <Spinner /> : i18next.t('ok')}
|
action={loading ? <Spinner /> : i18next.t('ok')}
|
||||||
done={async () => {
|
done={async () => {
|
||||||
@ -186,6 +187,7 @@ const URLSelector = ({
|
|||||||
</Txt>
|
</Txt>
|
||||||
<Input
|
<Input
|
||||||
value={imageURL}
|
value={imageURL}
|
||||||
|
data-testid="source-url-input"
|
||||||
placeholder={i18next.t('source.enterValidURL')}
|
placeholder={i18next.t('source.enterValidURL')}
|
||||||
type="text"
|
type="text"
|
||||||
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
@ -655,6 +657,7 @@ export class SourceSelector extends React.Component<
|
|||||||
disabled={this.state.imageSelectorOpen}
|
disabled={this.state.imageSelectorOpen}
|
||||||
primary={this.state.defaultFlowActive}
|
primary={this.state.defaultFlowActive}
|
||||||
key="Flash from file"
|
key="Flash from file"
|
||||||
|
data-testid="flash-from-file"
|
||||||
flow={{
|
flow={{
|
||||||
onClick: () => this.openImageSelector(),
|
onClick: () => this.openImageSelector(),
|
||||||
label: i18next.t('source.fromFile'),
|
label: i18next.t('source.fromFile'),
|
||||||
@ -665,6 +668,7 @@ export class SourceSelector extends React.Component<
|
|||||||
/>
|
/>
|
||||||
<FlowSelector
|
<FlowSelector
|
||||||
key="Flash from URL"
|
key="Flash from URL"
|
||||||
|
data-testid="flash-from-url"
|
||||||
flow={{
|
flow={{
|
||||||
onClick: () => this.openURLSelector(),
|
onClick: () => this.openURLSelector(),
|
||||||
label: i18next.t('source.fromURL'),
|
label: i18next.t('source.fromURL'),
|
||||||
|
@ -150,6 +150,7 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
|
|||||||
tabIndex={targets.length > 0 ? -1 : 2}
|
tabIndex={targets.length > 0 ? -1 : 2}
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
onClick={props.openDriveSelector}
|
onClick={props.openDriveSelector}
|
||||||
|
data-testid="select-target-button"
|
||||||
>
|
>
|
||||||
{i18next.t('target.selectTarget')}
|
{i18next.t('target.selectTarget')}
|
||||||
</StepButton>
|
</StepButton>
|
||||||
|
@ -25,6 +25,8 @@ import { geteuid, platform } from 'process';
|
|||||||
const adapters: Adapter[] = [
|
const adapters: Adapter[] = [
|
||||||
new BlockDeviceAdapter({
|
new BlockDeviceAdapter({
|
||||||
includeSystemDrives: () => true,
|
includeSystemDrives: () => true,
|
||||||
|
includeVirtualDrives: () =>
|
||||||
|
process.env.ETCHER_INCLUDE_VIRTUAL_DRIVES === '1',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
"package": "electron-forge package",
|
"package": "electron-forge package",
|
||||||
"start": "electron-forge start",
|
"start": "electron-forge start",
|
||||||
"make": "electron-forge make",
|
"make": "electron-forge make",
|
||||||
"wdio": "xvfb-maybe wdio run ./wdio.conf.ts"
|
"wdio": "xvfb-maybe wdio run ./wdio.conf.ts --suite gui --suite shared",
|
||||||
|
"wdio:e2e": "xvfb-maybe wdio run ./wdio.conf.ts --suite e2e",
|
||||||
|
"wdio:all": "xvfb-maybe wdio run ./wdio.conf.ts --suite gui --suite shared --suite e2e"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
42
tests/e2e/e2e-flash-from-file.spec.ts
Normal file
42
tests/e2e/e2e-flash-from-file.spec.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { browser } from '@wdio/globals';
|
||||||
|
|
||||||
|
describe('Electron Testing', () => {
|
||||||
|
it('should print application title', async () => {
|
||||||
|
console.log('Hello', await browser.getTitle(), 'application!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should "flash from file"', async () => {
|
||||||
|
const flashFromFileButton = $('button[data-testid="flash-from-file"]');
|
||||||
|
await flashFromFileButton.waitForDisplayed({ timeout: 10000 });
|
||||||
|
// const isDisplayed = await flashFromFileButton.isDisplayed();
|
||||||
|
await flashFromFileButton.click();
|
||||||
|
|
||||||
|
const selectTargetButton = $('button[data-testid="select-target-button"]');
|
||||||
|
await selectTargetButton.waitForClickable({ timeout: 30000 });
|
||||||
|
await selectTargetButton.click();
|
||||||
|
|
||||||
|
// TODO: Select target using ENV variable for the drive
|
||||||
|
const targetVirtualDrive = $('=/dev/disk8');
|
||||||
|
await targetVirtualDrive.waitForDisplayed({ timeout: 10000 });
|
||||||
|
await targetVirtualDrive.click();
|
||||||
|
|
||||||
|
const validateTargetButton = $(
|
||||||
|
'button[data-testid="validate-target-button"]',
|
||||||
|
);
|
||||||
|
await validateTargetButton.waitForClickable({ timeout: 10000 });
|
||||||
|
await validateTargetButton.click();
|
||||||
|
|
||||||
|
const flashNowButton = $('button[data-testid="flash-now-button"]');
|
||||||
|
await flashNowButton.waitForClickable({ timeout: 10000 });
|
||||||
|
await flashNowButton.click();
|
||||||
|
|
||||||
|
// FIXME: not able to find the flashResults :(
|
||||||
|
const flashResults = $('span[data-testid="flash-results"]');
|
||||||
|
await flashResults.waitForDisplayed({ timeout: 20000 });
|
||||||
|
|
||||||
|
expect(flashResults.getText()).toBe('Flash Completed!');
|
||||||
|
|
||||||
|
// we're good;
|
||||||
|
// now we should check the content of the image but we can do that outside wdio
|
||||||
|
});
|
||||||
|
});
|
61
tests/e2e/e2e-flash-from-url.spec.ts
Normal file
61
tests/e2e/e2e-flash-from-url.spec.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { browser } from '@wdio/globals';
|
||||||
|
|
||||||
|
describe('Electron Testing', () => {
|
||||||
|
it('should print application title', async () => {
|
||||||
|
console.log('Hello', await browser.getTitle(), 'application!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should "select an url source"', async () => {
|
||||||
|
const flashFromUrlButton = $('button[data-testid="flash-from-url"]');
|
||||||
|
await flashFromUrlButton.waitForDisplayed({ timeout: 10000 });
|
||||||
|
// const isDisplayed = await flashFromFileButton.isDisplayed();
|
||||||
|
await flashFromUrlButton.click();
|
||||||
|
|
||||||
|
const enterValidUrlInput = $('input[data-testid="source-url-input"]');
|
||||||
|
await enterValidUrlInput.waitForDisplayed({ timeout: 10000 });
|
||||||
|
|
||||||
|
// TODO: use an env variable for the URL
|
||||||
|
await enterValidUrlInput.setValue(
|
||||||
|
'https://api.balena-cloud.com/download?deviceType=raspberrypi4-64&version=5.2.8&fileType=.zip&developmentMode=true',
|
||||||
|
);
|
||||||
|
|
||||||
|
const sourceUrlOkButton = $('button[data-testid="source-url-ok-button"]');
|
||||||
|
await sourceUrlOkButton.waitForDisplayed({ timeout: 10000 });
|
||||||
|
await sourceUrlOkButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should "select a virtual target"', async () => {
|
||||||
|
const selectTargetButton = $('button[data-testid="select-target-button"]');
|
||||||
|
await selectTargetButton.waitForClickable({ timeout: 30000 });
|
||||||
|
await selectTargetButton.click();
|
||||||
|
|
||||||
|
// target drive is set in the github custom test action
|
||||||
|
// if you run the test locally, pass the varibale
|
||||||
|
const targetVirtualDrive = $(`=${process.env.TARGET_DRIVE}`);
|
||||||
|
await targetVirtualDrive.waitForDisplayed({ timeout: 10000 });
|
||||||
|
await targetVirtualDrive.click();
|
||||||
|
|
||||||
|
const validateTargetButton = $(
|
||||||
|
'button[data-testid="validate-target-button"]',
|
||||||
|
);
|
||||||
|
await validateTargetButton.waitForClickable({ timeout: 10000 });
|
||||||
|
await validateTargetButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should "start flashing"', async () => {
|
||||||
|
const flashNowButton = $('button[data-testid="flash-now-button"]');
|
||||||
|
await flashNowButton.waitForClickable({ timeout: 10000 });
|
||||||
|
await flashNowButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the "Flash Completed" screen', async () => {
|
||||||
|
const flashResults = $('[data-testid="flash-results"]');
|
||||||
|
await flashResults.waitForDisplayed({ timeout: 180000 });
|
||||||
|
|
||||||
|
const flashResultsText = await flashResults.getText();
|
||||||
|
expect(flashResultsText).toBe('Flash Completed!');
|
||||||
|
|
||||||
|
// we're good;
|
||||||
|
// now we should check the content of the image but we can do that outside wdio
|
||||||
|
});
|
||||||
|
});
|
@ -1,7 +0,0 @@
|
|||||||
import { browser } from '@wdio/globals';
|
|
||||||
|
|
||||||
describe('Electron Testing', () => {
|
|
||||||
it('should print application title', async () => {
|
|
||||||
console.log('Hello', await browser.getTitle(), 'application!');
|
|
||||||
});
|
|
||||||
});
|
|
29
wdio.conf.ts
29
wdio.conf.ts
@ -35,16 +35,25 @@ export const config: Options.Testrunner = {
|
|||||||
// Patterns to exclude.
|
// Patterns to exclude.
|
||||||
// FIXME: Remove the following exclusions once the tests are ported to WDIO
|
// FIXME: Remove the following exclusions once the tests are ported to WDIO
|
||||||
exclude: [
|
exclude: [
|
||||||
'tests/gui/modules/image-writer.spec.ts',
|
'./tests/gui/modules/image-writer.spec.ts',
|
||||||
'tests/gui/os/window-progress.spec.ts',
|
'./tests/gui/os/window-progress.spec.ts',
|
||||||
'tests/gui/models/available-drives.spec.ts',
|
'./tests/gui/models/available-drives.spec.ts',
|
||||||
'tests/gui/models/flash-state.spec.ts',
|
'./tests/gui/models/flash-state.spec.ts',
|
||||||
'tests/gui/models/selection-state.spec.ts',
|
'./tests/gui/models/selection-state.spec.ts',
|
||||||
'tests/gui/models/settings.spec.ts',
|
'./tests/gui/models/settings.spec.ts',
|
||||||
'tests/shared/drive-constraints.spec.ts',
|
'./tests/shared/drive-constraints.spec.ts',
|
||||||
'tests/shared/messages.spec.ts',
|
'./tests/shared/messages.spec.ts',
|
||||||
'tests/gui/modules/progress-status.spec.ts',
|
'./tests/gui/modules/progress-status.spec.ts',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
suites: {
|
||||||
|
'gui': ['./tests/gui/**/*.spec.ts'],
|
||||||
|
'shared': ['./tests/shared/**/*.spec.ts'],
|
||||||
|
'e2e': [
|
||||||
|
// 'tests/e2e/e2e-flash-from-file.spec.ts',
|
||||||
|
'./tests/e2e/e2e-flash-from-url.spec.ts',
|
||||||
|
],
|
||||||
|
},
|
||||||
//
|
//
|
||||||
// ============
|
// ============
|
||||||
// Capabilities
|
// Capabilities
|
||||||
@ -85,7 +94,7 @@ export const config: Options.Testrunner = {
|
|||||||
// Define all options that are relevant for the WebdriverIO instance here
|
// Define all options that are relevant for the WebdriverIO instance here
|
||||||
//
|
//
|
||||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||||
logLevel: 'info',
|
logLevel: 'warn',
|
||||||
//
|
//
|
||||||
// Set specific log levels per logger
|
// Set specific log levels per logger
|
||||||
// loggers:
|
// loggers:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user