一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
89 行
2.7 KiB
TypeScript
89 行
2.7 KiB
TypeScript
import { execCommand } from '@joplin/utils';
|
|
import { Second } from '@joplin/utils/time';
|
|
import { dirname, join } from 'path';
|
|
|
|
const getSimulator = async () => {
|
|
const simulators = JSON.parse(await execCommand('xcrun simctl list -j', { quiet: true }));
|
|
|
|
type Device = {
|
|
productFamily: string;
|
|
name: string;
|
|
};
|
|
const deviceTypes: Device[] = simulators.devicetypes;
|
|
// For now, always choose the first iOS simulator in the list
|
|
return deviceTypes
|
|
.find((device) => device.productFamily === 'iPhone')
|
|
.name;
|
|
};
|
|
|
|
|
|
// Based roughly on the approach taken by react-native-esbuild
|
|
// (https://github.com/oblador/react-native-esbuild/blob/e5897955357e3fe6a48e1dd90ccb909426d24566/package.json#L9)
|
|
const uiTests = async () => {
|
|
if (process.platform !== 'darwin') throw new Error('UI tests currently must run on MacOS');
|
|
|
|
const iosDir = join(dirname(__dirname), 'ios');
|
|
const workspaceFile = join(iosDir, 'Joplin.xcworkspace');
|
|
|
|
const env = {
|
|
RUNNING_UI_TESTS: '1',
|
|
XCODE_XCCONFIG_FILE: join(iosDir, 'testing.xcconfig'),
|
|
};
|
|
|
|
const sharedOptions = [
|
|
'-workspace',
|
|
workspaceFile,
|
|
'-scheme', 'Joplin',
|
|
// Create a release build: Without this, the React Native dev server needs to be running
|
|
// in the background:
|
|
'-configuration', 'Release',
|
|
'-destination', `platform=iOS Simulator,name=${await getSimulator()}`,
|
|
];
|
|
|
|
// Build first, in quiet mode, to avoid hundreds of thousands of lines of build output.
|
|
const buildStart = performance.now();
|
|
await execCommand([
|
|
'xcrun',
|
|
'xcodebuild',
|
|
'build-for-testing',
|
|
// Only log errors and warnings.
|
|
'-quiet',
|
|
...sharedOptions,
|
|
], { env });
|
|
const buildEnd = performance.now();
|
|
|
|
console.log('Finished build in', (buildEnd - buildStart) / Second, 'seconds');
|
|
|
|
// Run, with normal output. This makes it easier to debug test failures
|
|
const runUiTests = () => {
|
|
return execCommand([
|
|
'xcrun',
|
|
'xcodebuild',
|
|
'test-without-building',
|
|
...sharedOptions,
|
|
|
|
'-retry-tests-on-failure',
|
|
'-test-iterations', '3',
|
|
], { env });
|
|
};
|
|
|
|
// On CI, the test runner sometimes fails to bootstrap ("Early unexpected exit"), which
|
|
// -retry-tests-on-failure does not cover since no test ever started. Retrying the whole
|
|
// xcodebuild invocation, after resetting the simulators, works around it.
|
|
const maxAttempts = 3;
|
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
try {
|
|
await runUiTests();
|
|
return;
|
|
} catch (error) {
|
|
if (attempt === maxAttempts) throw error;
|
|
|
|
console.warn(`UI tests failed (attempt ${attempt} of ${maxAttempts}), retrying...`);
|
|
await execCommand(['xcrun', 'simctl', 'shutdown', 'all'], { quiet: true });
|
|
await execCommand(['xcrun', 'simctl', 'erase', 'all'], { quiet: true });
|
|
}
|
|
}
|
|
};
|
|
|
|
export default uiTests;
|