Skip to content

Commit 39bb73d

Browse files
authored
feat: pre-load attachments on ios simulators (#75)
* feat: create simulators pre-loaded with attachments remove reliance on hardcoded env vars no longer push attachments in tests, bloating the devices * fix: tidy up scripts * fix: ensure ci runs use json * feat: add ci simulators json file * fix: don't clean up json in local dev * chore: blacklist done/donate * refactor: move boot and shutdown to shared file * refactor dynamic simulator loading * feat: cycle through worker pool on retry * chore: make note of DEVICES_PER_TEST_COUNT * chore: tidy up comments * fix: remove env vars from android file too * fix: address PR comments eliminate unused variables, redundant deletion loops and types
1 parent 4c60061 commit 39bb73d

14 files changed

+535
-119
lines changed

.env.sample

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,6 @@ AVD_MANAGER_FULL_PATH=/home/yougotthis/Android/Sdk/cmdline-tools/latest/bin/avdm
77
EMULATOR_FULL_PATH=/home/yougotthis/Android/Sdk/emulator/emulator
88
ANDROID_SYSTEM_IMAGE="system-images;android-35;google_atd;x86_64"
99
APPIUM_ADB_FULL_PATH=/home/yougotthis/Android/sdk/platform-tools/adb
10-
IOS_1_SIMULATOR=just_not_empty
11-
IOS_2_SIMULATOR=just_not_empty
12-
IOS_3_SIMULATOR=just_not_empty
13-
IOS_4_SIMULATOR=just_not_empty
14-
IOS_5_SIMULATOR=just_not_empty
15-
IOS_6_SIMULATOR=just_not_empty
16-
IOS_7_SIMULATOR=just_not_empty
17-
IOS_8_SIMULATOR=just_not_empty
18-
IOS_9_SIMULATOR=just_not_empty
19-
IOS_10_SIMULATOR=just_not_empty
20-
IOS_11_SIMULATOR=just_not_empty
21-
IOS_12_SIMULATOR=just_not_empty
2210
PRINT_TEST_LOGS=true
2311
PRINT_ONGOING_TEST_LOGS = 1
2412
PRINT_FAILED_TEST_LOGS=1

.github/workflows/android-regression.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,6 @@ jobs:
7171
_TESTING: 1 # Always hide webdriver logs (@appium/support/ flag)
7272
PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL != 'minimal' && '1' || '0' }} # Show stdout/stderr if test fails (@session-foundation/playwright-reporter/ flag)
7373
PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'verbose' && '1' || '0' }} # Show everything as it happens (@session-foundation/playwright-reporter/ flag)
74-
IOS_1_SIMULATOR: '<just_not_empty>'
75-
IOS_2_SIMULATOR: '<just_not_empty>'
76-
IOS_3_SIMULATOR: '<just_not_empty>'
77-
IOS_4_SIMULATOR: '<just_not_empty>'
78-
IOS_5_SIMULATOR: '<just_not_empty>'
79-
IOS_6_SIMULATOR: '<just_not_empty>'
80-
IOS_7_SIMULATOR: '<just_not_empty>'
81-
IOS_8_SIMULATOR: '<just_not_empty>'
82-
IOS_9_SIMULATOR: '<just_not_empty>'
83-
IOS_10_SIMULATOR: '<just_not_empty>'
84-
IOS_11_SIMULATOR: '<just_not_empty>'
85-
IOS_12_SIMULATOR: '<just_not_empty>'
8674

8775
steps:
8876
- uses: actions/checkout@v4

.github/workflows/ios-regression.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,6 @@ jobs:
8686
AVD_MANAGER_FULL_PATH: '<just_not_empty>'
8787
ANDROID_SYSTEM_IMAGE: '<just_not_empty>'
8888
EMULATOR_FULL_PATH: '<just_not_empty>'
89-
IOS_1_SIMULATOR: '4A75A0E1-9EDE-4169-93C3-DCE0F0C7664F'
90-
IOS_2_SIMULATOR: 'ACB6A587-8556-4EA0-87CF-4326A9A22051'
91-
IOS_3_SIMULATOR: 'D90B2AE2-FF30-49BE-9370-B789BAEED3BB'
92-
IOS_4_SIMULATOR: '59BD1CA4-7A8D-40FB-BAC7-CC99500644E0'
93-
IOS_5_SIMULATOR: '064F4F80-B81C-4B72-9715-43CD18975139'
94-
IOS_6_SIMULATOR: '56D8BA2F-BA0C-4D8F-8E5B-FD928E2C7C66'
95-
IOS_7_SIMULATOR: '012D6656-D6DE-4932-A460-72F5629EB2E0'
96-
IOS_8_SIMULATOR: 'D66CBD9C-7550-4055-8504-95F0AE700617'
97-
IOS_9_SIMULATOR: '84884861-F8EF-4481-A001-B403F2649FCF'
98-
IOS_10_SIMULATOR: 'C0EE6A21-044D-4B6E-B9A5-7AB977ADF305'
99-
IOS_11_SIMULATOR: 'B8E78B21-1432-41F3-A398-DE4FF8CF9DED'
100-
IOS_12_SIMULATOR: '8214A3A2-D4E1-4AA8-BB0F-394E3A49BCFA'
10189

10290
steps:
10391
- uses: actions/checkout@v4

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ test-results/
3131
README.md
3232
package.json
3333
/avd/
34-
run/localizer/*
34+
run/localizer/*
35+
ci-simulators.json

ci-simulators.json

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
[
2+
{
3+
"name": "Auto-16PM-0",
4+
"udid": "4D0EDB6B-9517-4E9F-AD80-4853604401FB",
5+
"wdaPort": 1253
6+
},
7+
{
8+
"name": "Auto-16PM-1",
9+
"udid": "145BA489-0AAB-473F-9238-57C8AD75576A",
10+
"wdaPort": 1254
11+
},
12+
{
13+
"name": "Auto-16PM-2",
14+
"udid": "6F8F94E6-5623-4C8C-88A0-DAE34F343BCE",
15+
"wdaPort": 1255
16+
},
17+
{
18+
"name": "Auto-16PM-3",
19+
"udid": "5CFFE21B-26BE-4636-99FE-B5D7B8DC76C4",
20+
"wdaPort": 1256
21+
},
22+
{
23+
"name": "Auto-16PM-4",
24+
"udid": "570FEA9F-AFC2-4CCE-B637-290D0EE290C4",
25+
"wdaPort": 1257
26+
},
27+
{
28+
"name": "Auto-16PM-5",
29+
"udid": "09D47861-AF97-4D56-9DC1-9839168AA3CA",
30+
"wdaPort": 1258
31+
},
32+
{
33+
"name": "Auto-16PM-6",
34+
"udid": "3C7A031A-3224-40A9-86C7-BE64B8B6E0A2",
35+
"wdaPort": 1259
36+
},
37+
{
38+
"name": "Auto-16PM-7",
39+
"udid": "BA458AF8-C3F9-41E7-8B76-61157EA5EDF3",
40+
"wdaPort": 1260
41+
},
42+
{
43+
"name": "Auto-16PM-8",
44+
"udid": "5C799A8A-2AE0-4ED9-A077-BCC703ABF7E0",
45+
"wdaPort": 1261
46+
},
47+
{
48+
"name": "Auto-16PM-9",
49+
"udid": "AEE0AE84-26FA-42FE-85CD-82780DF1154C",
50+
"wdaPort": 1262
51+
},
52+
{
53+
"name": "Auto-16PM-10",
54+
"udid": "5B947D6C-DAE0-4066-9263-C2B3E1B4E970",
55+
"wdaPort": 1263
56+
},
57+
{
58+
"name": "Auto-16PM-11",
59+
"udid": "662F717D-A26D-47C7-A47B-E5090B1C4239",
60+
"wdaPort": 1264
61+
}
62+
]

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"name": "session-appium",
33
"version": "1.0.0",
44
"scripts": {
5+
"cleanup-simulators": "npx ts-node scripts/cleanup_ios_simulators.ts",
6+
"create-simulators": "yarn cleanup-simulators && npx ts-node scripts/create_ios_simulators.ts",
57
"lint": "yarn prettier . --write --cache && yarn eslint . --cache ",
68
"lint-check": "yarn prettier . --check && yarn eslint .",
79
"tsc": "tsc",

run/test/specs/user_actions_share_to_session.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async function shareToSession(platform: SupportedPlatformsType, testInfo: TestIn
4242
await sleepFor(1000);
4343
await alice1.pressHome();
4444
await sleepFor(2000);
45-
await alice1.pushMediaToDevice(testImage);
45+
await alice1.onAndroid().pushMediaToDevice(testImage); // iOS is preloaded
4646
// Photo app is on different page than Session
4747
await alice1.onIOS().swipeRightAny('Session');
4848
await alice1.clickOnElementAll(new PhotoLibrary(alice1));
Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { AppiumXCUITestCapabilities } from '@wdio/types/build/Capabilities';
22
import { W3CCapabilities } from '@wdio/types/build/Capabilities';
33
import dotenv from 'dotenv';
4+
import { existsSync, readFileSync } from 'fs';
45

56
import { IntRange } from '../../../types/RangeType';
7+
68
dotenv.config();
9+
710
const iosPathPrefix = process.env.IOS_APP_PATH_PREFIX;
811

912
if (!iosPathPrefix) {
@@ -16,8 +19,8 @@ console.log(`iOS app full path: ${iosAppFullPath}`);
1619
const sharediOSCapabilities: AppiumXCUITestCapabilities = {
1720
'appium:app': iosAppFullPath,
1821
'appium:platformName': 'iOS',
19-
'appium:platformVersion': '17.2',
20-
'appium:deviceName': 'iPhone 15 Pro Max',
22+
'appium:platformVersion': '18.3',
23+
'appium:deviceName': 'iPhone 16 Pro Max',
2124
'appium:automationName': 'XCUITest',
2225
'appium:bundleId': 'com.loki-project.loki-messenger',
2326
'appium:newCommandTimeout': 300000,
@@ -31,62 +34,92 @@ const sharediOSCapabilities: AppiumXCUITestCapabilities = {
3134
communityPollLimit: 5,
3235
},
3336
},
34-
// "appium:isHeadless": true,
3537
} as AppiumXCUITestCapabilities;
3638

37-
const envVars = [
38-
'IOS_1_SIMULATOR',
39-
'IOS_2_SIMULATOR',
40-
'IOS_3_SIMULATOR',
41-
'IOS_4_SIMULATOR',
42-
'IOS_5_SIMULATOR',
43-
'IOS_6_SIMULATOR',
44-
'IOS_7_SIMULATOR',
45-
'IOS_8_SIMULATOR',
46-
'IOS_9_SIMULATOR',
47-
'IOS_10_SIMULATOR',
48-
'IOS_11_SIMULATOR',
49-
'IOS_12_SIMULATOR',
50-
] as const;
51-
52-
function getIOSSimulatorUUIDFromEnv(index: CapabilitiesIndexType): string {
53-
const envVar = envVars[index];
54-
const uuid = process.env[envVar];
55-
56-
if (!uuid) {
57-
throw new Error(`Environment variable ${envVar} is not set`);
39+
export type Simulator = {
40+
name: string;
41+
udid: string;
42+
wdaPort: number;
43+
};
44+
45+
function loadSimulators(): Simulator[] {
46+
const jsonPath = 'ci-simulators.json';
47+
48+
// Load from .env variables
49+
const envVars = [
50+
'IOS_1_SIMULATOR',
51+
'IOS_2_SIMULATOR',
52+
'IOS_3_SIMULATOR',
53+
'IOS_4_SIMULATOR',
54+
'IOS_5_SIMULATOR',
55+
'IOS_6_SIMULATOR',
56+
'IOS_7_SIMULATOR',
57+
'IOS_8_SIMULATOR',
58+
'IOS_9_SIMULATOR',
59+
'IOS_10_SIMULATOR',
60+
'IOS_11_SIMULATOR',
61+
'IOS_12_SIMULATOR',
62+
];
63+
64+
const simulators = envVars
65+
.map((envVar, index) => {
66+
const udid = process.env[envVar];
67+
if (!udid) return null; // No need for all 12 sim variables to be set
68+
return { name: `Sim-${index + 1}`, udid, wdaPort: 1253 + index };
69+
})
70+
.filter((sim): sim is Simulator => sim !== null);
71+
72+
// If we have simulators from env, use them (local dev)
73+
if (simulators.length > 0) {
74+
console.log(`Using ${simulators.length} simulators from .env file`);
75+
return simulators;
76+
}
77+
78+
// No env simulators - check if we're on CI
79+
if (process.env.CI === '1') {
80+
// CI should use JSON
81+
if (existsSync(jsonPath)) {
82+
console.log('Using simulators from ios-simulators.json (CI)');
83+
const sims: Simulator[] = JSON.parse(readFileSync(jsonPath, 'utf-8'));
84+
return sims;
85+
}
86+
throw new Error('CI mode: ios-simulators.json not found');
5887
}
5988

60-
return uuid;
89+
// Local dev with no .env entries
90+
throw new Error(
91+
'No iOS simulators found in .env\n' +
92+
'Run: yarn create-simulators <number>\n' +
93+
'Example: yarn create-simulators 4'
94+
);
6195
}
62-
const MAX_CAPABILITIES_INDEX = envVars.length;
96+
const simulators = loadSimulators();
97+
98+
const capabilities = simulators.map(sim => ({
99+
...sharediOSCapabilities,
100+
'appium:udid': sim.udid,
101+
'appium:wdaLocalPort': sim.wdaPort,
102+
}));
103+
104+
// Use a constant max that matches the envVars array length for type safety
105+
const _MAX_CAPABILITIES_INDEX = 12 as const;
63106

64-
export type CapabilitiesIndexType = IntRange<0, typeof MAX_CAPABILITIES_INDEX>;
107+
// For runtime validation, check against actual loaded simulators
108+
export const getMaxCapabilitiesIndex = () => capabilities.length;
109+
110+
// Type is still based on the constant for compile-time safety
111+
export type CapabilitiesIndexType = IntRange<0, typeof _MAX_CAPABILITIES_INDEX>;
65112

66113
export function capabilityIsValid(
67114
capabilitiesIndex: number
68115
): capabilitiesIndex is CapabilitiesIndexType {
69-
if (capabilitiesIndex < 0 || capabilitiesIndex > MAX_CAPABILITIES_INDEX) {
116+
// Runtime validation against actual loaded capabilities
117+
if (capabilitiesIndex < 0 || capabilitiesIndex >= capabilities.length) {
70118
return false;
71119
}
72120
return true;
73121
}
74122

75-
interface CustomW3CCapabilities extends W3CCapabilities {
76-
'appium:wdaLocalPort': number;
77-
'appium:udid': string;
78-
}
79-
80-
const emulatorUUIDs = Array.from({ length: MAX_CAPABILITIES_INDEX }, (_, index) =>
81-
getIOSSimulatorUUIDFromEnv(index as CapabilitiesIndexType)
82-
);
83-
84-
const capabilities = emulatorUUIDs.map((udid, index) => ({
85-
...sharediOSCapabilities,
86-
'appium:udid': udid,
87-
'appium:wdaLocalPort': 1253 + index,
88-
}));
89-
90123
export function getIosCapabilities(capabilitiesIndex: CapabilitiesIndexType): W3CCapabilities {
91124
if (capabilitiesIndex >= capabilities.length) {
92125
throw new Error(
@@ -102,11 +135,11 @@ export function getIosCapabilities(capabilitiesIndex: CapabilitiesIndexType): W3
102135
};
103136
}
104137

105-
export function getCapabilitiesForWorker(workerId: number): CustomW3CCapabilities {
138+
export function getCapabilitiesForWorker(workerId: number) {
106139
const emulator = capabilities[workerId % capabilities.length];
107140
return {
108141
...sharediOSCapabilities,
109142
'appium:udid': emulator['appium:udid'],
110143
'appium:wdaLocalPort': emulator['appium:wdaLocalPort'],
111-
} as CustomW3CCapabilities;
144+
};
112145
}

run/test/specs/utils/open_app.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,27 @@ const openiOSApp = async (
292292
}> => {
293293
console.info('openiOSApp');
294294

295-
// Calculate the actual capabilities index for the current worker
296-
const actualCapabilitiesIndex =
297-
capabilitiesIndex + getDevicesPerTestCount() * parseInt(process.env.TEST_PARALLEL_INDEX || '0');
295+
const parallelIndex = parseInt(process.env.TEST_PARALLEL_INDEX || '0');
296+
297+
// NOTE: This assumes DEVICES_PER_TEST_COUNT=4 is set in CI for iOS (not applicable to Android)
298+
// Worker pools are fixed at 4 devices each regardless of actual test size:
299+
// Worker 0: devices 0-3, Worker 1: devices 4-7, Worker 2: devices 8-11
300+
const devicesPerWorker = getDevicesPerTestCount();
301+
const workerBaseOffset = devicesPerWorker * parallelIndex;
302+
303+
// Apply retry offset, but wrap within the worker's device pool only
304+
// This means when retrying, alice/bob etc won't be the same device as before within a worker's pool
305+
// This is to avoid any issues where a device might be in a bad state for some reason
306+
// (e.g. not accessing photo library on iOS)
307+
const retryOffset = testInfo.retry || 0;
308+
const deviceIndexWithinWorker = (capabilitiesIndex + retryOffset) % devicesPerWorker;
309+
const actualCapabilitiesIndex = workerBaseOffset + deviceIndexWithinWorker;
310+
311+
if (retryOffset > 0) {
312+
console.info(
313+
`Retry offset applied (#${retryOffset}), rotating device allocations within worker`
314+
);
315+
}
298316

299317
const opts: XCUITestDriverOpts = {
300318
address: `http://localhost:${APPIUM_PORT}`,

0 commit comments

Comments
 (0)