From 8dcd1c0c5418c0e5ef5a715da092ef1173418e1c Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 5 Jul 2026 18:29:15 +0200 Subject: [PATCH 1/3] chore: Drop mocha --- .mocharc.js | 4 -- package.json | 7 +- ...simctl-e2e-specs.ts => simctl-e2e.spec.ts} | 60 ++--------------- test/unit/{simctl-specs.ts => simctl.spec.ts} | 65 ++++++++++--------- tsconfig.json | 8 +-- 5 files changed, 45 insertions(+), 99 deletions(-) delete mode 100644 .mocharc.js rename test/e2e/{simctl-e2e-specs.ts => simctl-e2e.spec.ts} (85%) rename test/unit/{simctl-specs.ts => simctl.spec.ts} (89%) diff --git a/.mocharc.js b/.mocharc.js deleted file mode 100644 index 40599e9..0000000 --- a/.mocharc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - require: ['ts-node/register'], - forbidOnly: Boolean(process.env.CI) -}; diff --git a/package.json b/package.json index 8511b7c..6b9ac05 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,8 @@ "format": "prettier -w ./lib ./test", "format:check": "prettier --check ./lib ./test", "prepare": "npm run build", - "test": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.ts\"", - "e2e-test": "mocha --exit --timeout 5m \"./test/e2e/**/*-specs.ts\"" + "test": "node --test --test-timeout=60000 \"./build/test/unit/**/*.spec.js\"", + "e2e-test": "node --test --test-force-exit --test-concurrency=1 --test-timeout=300000 \"./build/test/e2e/**/*.spec.js\"" }, "prettier": { "bracketSpacing": false, @@ -64,7 +64,6 @@ "@semantic-release/git": "^10.0.1", "@types/chai": "^5.2.3", "@types/chai-as-promised": "^8.0.2", - "@types/mocha": "^10.0.1", "@types/node": "^26.0.0", "@types/semver": "^7.7.1", "@types/sinon": "^22.0.0", @@ -73,11 +72,9 @@ "chai": "^6.0.0", "chai-as-promised": "^8.0.0", "conventional-changelog-conventionalcommits": "^9.3.1", - "mocha": "^11.0.1", "prettier": "^3.0.0", "semantic-release": "^25.0.2", "sinon": "^22.0.0", - "ts-node": "^10.9.1", "typescript": "^6.0.2" }, "types": "./build/lib/index.d.ts" diff --git a/test/e2e/simctl-e2e-specs.ts b/test/e2e/simctl-e2e.spec.ts similarity index 85% rename from test/e2e/simctl-e2e-specs.ts rename to test/e2e/simctl-e2e.spec.ts index 9455fad..31dc1ec 100644 --- a/test/e2e/simctl-e2e-specs.ts +++ b/test/e2e/simctl-e2e.spec.ts @@ -1,6 +1,4 @@ import {Simctl} from '../../lib/simctl'; -import xcode from 'appium-xcode'; -import {retryInterval} from 'asyncbox'; import {rimraf} from 'rimraf'; import {randomUUID} from 'node:crypto'; import path from 'node:path'; @@ -8,14 +6,13 @@ import os from 'node:os'; import fs from 'node:fs/promises'; import {expect, use} from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import {describe, it, beforeEach, afterEach, after, before} from 'node:test'; use(chaiAsPromised); +const E2E_TIMEOUT_MS = 200000; -describe('simctl', function () { +describe('simctl', {timeout: E2E_TIMEOUT_MS}, function () { const DEVICE_NAME = process.env.DEVICE_NAME || 'iPhone 17'; - const MOCHA_TIMEOUT = 200000; - this.timeout(MOCHA_TIMEOUT); - let randName: string; let validSdks: string[] = []; let sdk: string; @@ -132,27 +129,12 @@ describe('simctl', function () { }); describe('on running Simulator', function () { - if (process.env.TRAVIS) { - this.retries(3); - } - - let major: number, minor: number; - before(async function () { - const version = await xcode.getVersion(true); - if (typeof version === 'string') { - return this.skip(); - } - ({major, minor} = version); - if (major < 8 || (major === 8 && minor < 1)) { - return this.skip(); - } - const sdk = process.env.IOS_SDK || validSdks.at(-1); simctl.udid = await simctl.createDevice('runningSimTest', DEVICE_NAME, sdk!); await simctl.bootDevice(); - await simctl.startBootMonitor({timeout: MOCHA_TIMEOUT}); + await simctl.startBootMonitor({timeout: E2E_TIMEOUT_MS}); }); after(async function () { if (simctl.udid) { @@ -166,15 +148,9 @@ describe('simctl', function () { describe('startBootMonitor', function () { it('should be fulfilled if the simulator is already booted', async function () { - if (major < 8 || (major === 8 && minor < 1)) { - return this.skip(); - } await expect(simctl.startBootMonitor()).to.eventually.be.fulfilled; }); it('should fail to monitor booting of non-existing simulator', async function () { - if (major < 8 || (major === 8 && minor < 1)) { - return this.skip(); - } const udid = simctl.udid; try { simctl.udid = 'blabla'; @@ -186,28 +162,12 @@ describe('simctl', function () { }); describe('pasteboard', function () { - let pbRetries = 0; - before(function () { - if (major < 8 || (major === 8 && minor < 1)) { - return this.skip(); - } - if (major === 9) { - if (process.env.TRAVIS) { - return this.skip(); - } - // TODO: recheck when full Xcode 9 comes out to see if pasteboard works better - pbRetries = 200; - this.timeout(200 * 1000 * 2); - } - }); it('should set and get the content of the pasteboard', async function () { const pbContent = 'blablabla'; const encoding = 'ascii'; - await retryInterval(pbRetries, 1000, async () => { - await simctl.setPasteboard(pbContent, encoding); - expect(await simctl.getPasteboard(encoding)).to.eql(pbContent); - }); + await simctl.setPasteboard(pbContent, encoding); + expect(await simctl.getPasteboard(encoding)).to.eql(pbContent); }); }); @@ -216,9 +176,6 @@ describe('simctl', function () { 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; let picturePath: string | undefined; before(async function () { - if (major < 8 || (major === 8 && minor < 1)) { - return this.skip(); - } picturePath = path.join(os.tmpdir(), `${randomUUID()}.png`); await fs.writeFile( picturePath, @@ -299,11 +256,6 @@ describe('simctl', function () { describe('pushNotification', function () { it('should not throw an error when sending a push notification', async function () { - if (process.env.CI) { - // This test is unstable in CI env - return this.skip(); - } - const payload = { 'Simulator Target Bundle': 'com.apple.Preferences', aps: { diff --git a/test/unit/simctl-specs.ts b/test/unit/simctl.spec.ts similarity index 89% rename from test/unit/simctl-specs.ts rename to test/unit/simctl.spec.ts index 40c5dd1..71762be 100644 --- a/test/unit/simctl-specs.ts +++ b/test/unit/simctl.spec.ts @@ -4,40 +4,14 @@ import path from 'node:path'; import {expect, use} from 'chai'; import chaiAsPromised from 'chai-as-promised'; import {Simctl} from '../../lib/simctl'; +import {describe, it, beforeEach, afterEach, after} from 'node:test'; use(chaiAsPromised); - -// @ts-ignore - __dirname is available in CommonJS -const testDir = - typeof __dirname !== 'undefined' ? __dirname : path.dirname(require.resolve('./simctl-specs.ts')); - -const devicePayloads = [ - [ - { - stdout: fs.readFileSync(path.join(testDir, 'fixtures/devices.json'), 'utf-8'), - }, - { - stdout: fs.readFileSync( - path.join(__dirname, 'fixtures/devices-with-unavailable.json'), - 'utf-8', - ), - }, - ], - [ - { - stdout: fs.readFileSync(path.join(__dirname, 'fixtures/devices-simple.json'), 'utf-8'), - }, - { - stdout: fs.readFileSync( - path.join(__dirname, 'fixtures/devices-with-unavailable-simple.json'), - 'utf-8', - ), - }, - ], -]; +const MODULE_NAME = 'node-simctl'; describe('simctl', function () { let execStub: sinon.SinonStub; + const devicePayloads = getDevicePayloadsSync(); function stubSimctl(xcrun: {path?: string | null} = {}) { const simctl = new Simctl({xcrun: {path: xcrun.path ?? null}}); @@ -345,3 +319,36 @@ describe('simctl', function () { }); }); }); + +function getModuleRootSync(): string { + let currentDir = path.dirname(path.resolve(__filename)); + let isAtFsRoot = false; + while (!isAtFsRoot) { + const manifestPath = path.join(currentDir, 'package.json'); + try { + if (fs.existsSync(manifestPath)) { + const {name} = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {name?: string}; + if (name === MODULE_NAME) { + return currentDir; + } + } + } catch { + // ignore + } + currentDir = path.dirname(currentDir); + isAtFsRoot = currentDir.length <= path.dirname(currentDir).length; + } + throw new Error('Module root cannot be found'); +} + +function getDevicePayloadsSync(): {stdout: string}[][] { + const fixturesRoot = path.join(getModuleRootSync(), 'test', 'unit', 'fixtures'); + const payloads: string[] = [ + 'devices.json', + 'devices-with-unavailable.json', + 'devices-simple.json', + 'devices-with-unavailable-simple.json', + ].map((file) => fs.readFileSync(path.join(fixturesRoot, file), 'utf-8')); + const mapper = (payload: string) => ({stdout: payload}); + return [[payloads[0], payloads[1]].map(mapper), [payloads[2], payloads[3]].map(mapper)]; +} diff --git a/tsconfig.json b/tsconfig.json index 1e5a50e..f1aed4a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,15 +4,9 @@ "compilerOptions": { "esModuleInterop": true, "outDir": "build", - "types": ["node", "mocha"], + "types": ["node"], "strict": true }, - "ts-node": { - "transpileOnly": true, - "compilerOptions": { - "rootDir": "." - } - }, "include": [ "lib", "test" From c0de1a8cd756e5f1ebb833eb9709e7f5fc229423 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 5 Jul 2026 18:39:34 +0200 Subject: [PATCH 2/3] restore retries --- test/e2e/simctl-e2e.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/e2e/simctl-e2e.spec.ts b/test/e2e/simctl-e2e.spec.ts index 31dc1ec..307a3bf 100644 --- a/test/e2e/simctl-e2e.spec.ts +++ b/test/e2e/simctl-e2e.spec.ts @@ -7,6 +7,7 @@ import fs from 'node:fs/promises'; import {expect, use} from 'chai'; import chaiAsPromised from 'chai-as-promised'; import {describe, it, beforeEach, afterEach, after, before} from 'node:test'; +import {retryInterval} from 'asyncbox'; use(chaiAsPromised); const E2E_TIMEOUT_MS = 200000; @@ -166,8 +167,10 @@ describe('simctl', {timeout: E2E_TIMEOUT_MS}, function () { const pbContent = 'blablabla'; const encoding = 'ascii'; - await simctl.setPasteboard(pbContent, encoding); - expect(await simctl.getPasteboard(encoding)).to.eql(pbContent); + await retryInterval(60, 1000, async () => { + await simctl.setPasteboard(pbContent, encoding); + expect(await simctl.getPasteboard(encoding)).to.eql(pbContent); + }); }); }); From 8435724c22c490012f5a06eb1a6945574d91cfa3 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 5 Jul 2026 18:46:27 +0200 Subject: [PATCH 3/3] timeout --- test/e2e/simctl-e2e.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/e2e/simctl-e2e.spec.ts b/test/e2e/simctl-e2e.spec.ts index 307a3bf..fe53f37 100644 --- a/test/e2e/simctl-e2e.spec.ts +++ b/test/e2e/simctl-e2e.spec.ts @@ -10,9 +10,9 @@ import {describe, it, beforeEach, afterEach, after, before} from 'node:test'; import {retryInterval} from 'asyncbox'; use(chaiAsPromised); -const E2E_TIMEOUT_MS = 200000; +const BOOT_TIMEOUT_MS = 200000; -describe('simctl', {timeout: E2E_TIMEOUT_MS}, function () { +describe('simctl', function () { const DEVICE_NAME = process.env.DEVICE_NAME || 'iPhone 17'; let randName: string; let validSdks: string[] = []; @@ -135,7 +135,7 @@ describe('simctl', {timeout: E2E_TIMEOUT_MS}, function () { simctl.udid = await simctl.createDevice('runningSimTest', DEVICE_NAME, sdk!); await simctl.bootDevice(); - await simctl.startBootMonitor({timeout: E2E_TIMEOUT_MS}); + await simctl.startBootMonitor({timeout: BOOT_TIMEOUT_MS}); }); after(async function () { if (simctl.udid) { @@ -167,7 +167,7 @@ describe('simctl', {timeout: E2E_TIMEOUT_MS}, function () { const pbContent = 'blablabla'; const encoding = 'ascii'; - await retryInterval(60, 1000, async () => { + await retryInterval(10, 1000, async () => { await simctl.setPasteboard(pbContent, encoding); expect(await simctl.getPasteboard(encoding)).to.eql(pbContent); });