Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,6 @@ AVD_MANAGER_FULL_PATH=/home/yougotthis/Android/Sdk/cmdline-tools/latest/bin/avdm
EMULATOR_FULL_PATH=/home/yougotthis/Android/Sdk/emulator/emulator
ANDROID_SYSTEM_IMAGE="system-images;android-35;google_atd;x86_64"
APPIUM_ADB_FULL_PATH=/home/yougotthis/Android/sdk/platform-tools/adb
IOS_1_SIMULATOR=just_not_empty
IOS_2_SIMULATOR=just_not_empty
IOS_3_SIMULATOR=just_not_empty
IOS_4_SIMULATOR=just_not_empty
IOS_5_SIMULATOR=just_not_empty
IOS_6_SIMULATOR=just_not_empty
IOS_7_SIMULATOR=just_not_empty
IOS_8_SIMULATOR=just_not_empty
IOS_9_SIMULATOR=just_not_empty
IOS_10_SIMULATOR=just_not_empty
IOS_11_SIMULATOR=just_not_empty
IOS_12_SIMULATOR=just_not_empty
PRINT_TEST_LOGS=true
PRINT_ONGOING_TEST_LOGS = 1
PRINT_FAILED_TEST_LOGS=1
Expand Down
12 changes: 0 additions & 12 deletions .github/workflows/android-regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,6 @@ jobs:
_TESTING: 1 # Always hide webdriver logs (@appium/support/ flag)
PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL != 'minimal' && '1' || '0' }} # Show stdout/stderr if test fails (@session-foundation/playwright-reporter/ flag)
PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'verbose' && '1' || '0' }} # Show everything as it happens (@session-foundation/playwright-reporter/ flag)
IOS_1_SIMULATOR: '<just_not_empty>'
IOS_2_SIMULATOR: '<just_not_empty>'
IOS_3_SIMULATOR: '<just_not_empty>'
IOS_4_SIMULATOR: '<just_not_empty>'
IOS_5_SIMULATOR: '<just_not_empty>'
IOS_6_SIMULATOR: '<just_not_empty>'
IOS_7_SIMULATOR: '<just_not_empty>'
IOS_8_SIMULATOR: '<just_not_empty>'
IOS_9_SIMULATOR: '<just_not_empty>'
IOS_10_SIMULATOR: '<just_not_empty>'
IOS_11_SIMULATOR: '<just_not_empty>'
IOS_12_SIMULATOR: '<just_not_empty>'

steps:
- uses: actions/checkout@v4
Expand Down
12 changes: 0 additions & 12 deletions .github/workflows/ios-regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,6 @@ jobs:
AVD_MANAGER_FULL_PATH: '<just_not_empty>'
ANDROID_SYSTEM_IMAGE: '<just_not_empty>'
EMULATOR_FULL_PATH: '<just_not_empty>'
IOS_1_SIMULATOR: '4A75A0E1-9EDE-4169-93C3-DCE0F0C7664F'
IOS_2_SIMULATOR: 'ACB6A587-8556-4EA0-87CF-4326A9A22051'
IOS_3_SIMULATOR: 'D90B2AE2-FF30-49BE-9370-B789BAEED3BB'
IOS_4_SIMULATOR: '59BD1CA4-7A8D-40FB-BAC7-CC99500644E0'
IOS_5_SIMULATOR: '064F4F80-B81C-4B72-9715-43CD18975139'
IOS_6_SIMULATOR: '56D8BA2F-BA0C-4D8F-8E5B-FD928E2C7C66'
IOS_7_SIMULATOR: '012D6656-D6DE-4932-A460-72F5629EB2E0'
IOS_8_SIMULATOR: 'D66CBD9C-7550-4055-8504-95F0AE700617'
IOS_9_SIMULATOR: '84884861-F8EF-4481-A001-B403F2649FCF'
IOS_10_SIMULATOR: 'C0EE6A21-044D-4B6E-B9A5-7AB977ADF305'
IOS_11_SIMULATOR: 'B8E78B21-1432-41F3-A398-DE4FF8CF9DED'
IOS_12_SIMULATOR: '8214A3A2-D4E1-4AA8-BB0F-394E3A49BCFA'

steps:
- uses: actions/checkout@v4
Expand Down
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ test-results/
README.md
package.json
/avd/
run/localizer/*
run/localizer/*
ci-simulators.json
62 changes: 62 additions & 0 deletions ci-simulators.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[
{
"name": "Auto-16PM-0",
"udid": "4D0EDB6B-9517-4E9F-AD80-4853604401FB",
"wdaPort": 1253
},
{
"name": "Auto-16PM-1",
"udid": "145BA489-0AAB-473F-9238-57C8AD75576A",
"wdaPort": 1254
},
{
"name": "Auto-16PM-2",
"udid": "6F8F94E6-5623-4C8C-88A0-DAE34F343BCE",
"wdaPort": 1255
},
{
"name": "Auto-16PM-3",
"udid": "5CFFE21B-26BE-4636-99FE-B5D7B8DC76C4",
"wdaPort": 1256
},
{
"name": "Auto-16PM-4",
"udid": "570FEA9F-AFC2-4CCE-B637-290D0EE290C4",
"wdaPort": 1257
},
{
"name": "Auto-16PM-5",
"udid": "09D47861-AF97-4D56-9DC1-9839168AA3CA",
"wdaPort": 1258
},
{
"name": "Auto-16PM-6",
"udid": "3C7A031A-3224-40A9-86C7-BE64B8B6E0A2",
"wdaPort": 1259
},
{
"name": "Auto-16PM-7",
"udid": "BA458AF8-C3F9-41E7-8B76-61157EA5EDF3",
"wdaPort": 1260
},
{
"name": "Auto-16PM-8",
"udid": "5C799A8A-2AE0-4ED9-A077-BCC703ABF7E0",
"wdaPort": 1261
},
{
"name": "Auto-16PM-9",
"udid": "AEE0AE84-26FA-42FE-85CD-82780DF1154C",
"wdaPort": 1262
},
{
"name": "Auto-16PM-10",
"udid": "5B947D6C-DAE0-4066-9263-C2B3E1B4E970",
"wdaPort": 1263
},
{
"name": "Auto-16PM-11",
"udid": "662F717D-A26D-47C7-A47B-E5090B1C4239",
"wdaPort": 1264
}
]
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"name": "session-appium",
"version": "1.0.0",
"scripts": {
"cleanup-simulators": "npx ts-node scripts/cleanup_ios_simulators.ts",
"create-simulators": "yarn cleanup-simulators && npx ts-node scripts/create_ios_simulators.ts",
"lint": "yarn prettier . --write --cache && yarn eslint . --cache ",
"lint-check": "yarn prettier . --check && yarn eslint .",
"tsc": "tsc",
Expand Down
2 changes: 1 addition & 1 deletion run/test/specs/user_actions_share_to_session.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async function shareToSession(platform: SupportedPlatformsType, testInfo: TestIn
await sleepFor(1000);
await alice1.pressHome();
await sleepFor(2000);
await alice1.pushMediaToDevice(testImage);
await alice1.onAndroid().pushMediaToDevice(testImage); // iOS is preloaded
// Photo app is on different page than Session
await alice1.onIOS().swipeRightAny('Session');
await alice1.clickOnElementAll(new PhotoLibrary(alice1));
Expand Down
123 changes: 78 additions & 45 deletions run/test/specs/utils/capabilities_ios.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { AppiumXCUITestCapabilities } from '@wdio/types/build/Capabilities';
import { W3CCapabilities } from '@wdio/types/build/Capabilities';
import dotenv from 'dotenv';
import { existsSync, readFileSync } from 'fs';

import { IntRange } from '../../../types/RangeType';

dotenv.config();

const iosPathPrefix = process.env.IOS_APP_PATH_PREFIX;

if (!iosPathPrefix) {
Expand All @@ -16,8 +19,8 @@ console.log(`iOS app full path: ${iosAppFullPath}`);
const sharediOSCapabilities: AppiumXCUITestCapabilities = {
'appium:app': iosAppFullPath,
'appium:platformName': 'iOS',
'appium:platformVersion': '17.2',
'appium:deviceName': 'iPhone 15 Pro Max',
'appium:platformVersion': '18.3',
'appium:deviceName': 'iPhone 16 Pro Max',
'appium:automationName': 'XCUITest',
'appium:bundleId': 'com.loki-project.loki-messenger',
'appium:newCommandTimeout': 300000,
Expand All @@ -31,62 +34,92 @@ const sharediOSCapabilities: AppiumXCUITestCapabilities = {
communityPollLimit: 5,
},
},
// "appium:isHeadless": true,
} as AppiumXCUITestCapabilities;

const envVars = [
'IOS_1_SIMULATOR',
'IOS_2_SIMULATOR',
'IOS_3_SIMULATOR',
'IOS_4_SIMULATOR',
'IOS_5_SIMULATOR',
'IOS_6_SIMULATOR',
'IOS_7_SIMULATOR',
'IOS_8_SIMULATOR',
'IOS_9_SIMULATOR',
'IOS_10_SIMULATOR',
'IOS_11_SIMULATOR',
'IOS_12_SIMULATOR',
] as const;

function getIOSSimulatorUUIDFromEnv(index: CapabilitiesIndexType): string {
const envVar = envVars[index];
const uuid = process.env[envVar];

if (!uuid) {
throw new Error(`Environment variable ${envVar} is not set`);
export type Simulator = {
name: string;
udid: string;
wdaPort: number;
};

function loadSimulators(): Simulator[] {
const jsonPath = 'ci-simulators.json';

// Load from .env variables
const envVars = [
'IOS_1_SIMULATOR',
'IOS_2_SIMULATOR',
'IOS_3_SIMULATOR',
'IOS_4_SIMULATOR',
'IOS_5_SIMULATOR',
'IOS_6_SIMULATOR',
'IOS_7_SIMULATOR',
'IOS_8_SIMULATOR',
'IOS_9_SIMULATOR',
'IOS_10_SIMULATOR',
'IOS_11_SIMULATOR',
'IOS_12_SIMULATOR',
];

const simulators = envVars
.map((envVar, index) => {
const udid = process.env[envVar];
if (!udid) return null; // No need for all 12 sim variables to be set
return { name: `Sim-${index + 1}`, udid, wdaPort: 1253 + index };
})
.filter((sim): sim is Simulator => sim !== null);

// If we have simulators from env, use them (local dev)
if (simulators.length > 0) {
console.log(`Using ${simulators.length} simulators from .env file`);
return simulators;
}

// No env simulators - check if we're on CI
if (process.env.CI === '1') {
// CI should use JSON
if (existsSync(jsonPath)) {
console.log('Using simulators from ios-simulators.json (CI)');
const sims: Simulator[] = JSON.parse(readFileSync(jsonPath, 'utf-8'));
return sims;
}
throw new Error('CI mode: ios-simulators.json not found');
}

return uuid;
// Local dev with no .env entries
throw new Error(
'No iOS simulators found in .env\n' +
'Run: yarn create-simulators <number>\n' +
'Example: yarn create-simulators 4'
);
}
const MAX_CAPABILITIES_INDEX = envVars.length;
const simulators = loadSimulators();

const capabilities = simulators.map(sim => ({
...sharediOSCapabilities,
'appium:udid': sim.udid,
'appium:wdaLocalPort': sim.wdaPort,
}));

// Use a constant max that matches the envVars array length for type safety
const _MAX_CAPABILITIES_INDEX = 12 as const;

export type CapabilitiesIndexType = IntRange<0, typeof MAX_CAPABILITIES_INDEX>;
// For runtime validation, check against actual loaded simulators
export const getMaxCapabilitiesIndex = () => capabilities.length;

// Type is still based on the constant for compile-time safety
export type CapabilitiesIndexType = IntRange<0, typeof _MAX_CAPABILITIES_INDEX>;

export function capabilityIsValid(
capabilitiesIndex: number
): capabilitiesIndex is CapabilitiesIndexType {
if (capabilitiesIndex < 0 || capabilitiesIndex > MAX_CAPABILITIES_INDEX) {
// Runtime validation against actual loaded capabilities
if (capabilitiesIndex < 0 || capabilitiesIndex >= capabilities.length) {
return false;
}
return true;
}

interface CustomW3CCapabilities extends W3CCapabilities {
'appium:wdaLocalPort': number;
'appium:udid': string;
}

const emulatorUUIDs = Array.from({ length: MAX_CAPABILITIES_INDEX }, (_, index) =>
getIOSSimulatorUUIDFromEnv(index as CapabilitiesIndexType)
);

const capabilities = emulatorUUIDs.map((udid, index) => ({
...sharediOSCapabilities,
'appium:udid': udid,
'appium:wdaLocalPort': 1253 + index,
}));

export function getIosCapabilities(capabilitiesIndex: CapabilitiesIndexType): W3CCapabilities {
if (capabilitiesIndex >= capabilities.length) {
throw new Error(
Expand All @@ -102,11 +135,11 @@ export function getIosCapabilities(capabilitiesIndex: CapabilitiesIndexType): W3
};
}

export function getCapabilitiesForWorker(workerId: number): CustomW3CCapabilities {
export function getCapabilitiesForWorker(workerId: number) {
const emulator = capabilities[workerId % capabilities.length];
return {
...sharediOSCapabilities,
'appium:udid': emulator['appium:udid'],
'appium:wdaLocalPort': emulator['appium:wdaLocalPort'],
} as CustomW3CCapabilities;
};
}
24 changes: 21 additions & 3 deletions run/test/specs/utils/open_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,27 @@ const openiOSApp = async (
}> => {
console.info('openiOSApp');

// Calculate the actual capabilities index for the current worker
const actualCapabilitiesIndex =
capabilitiesIndex + getDevicesPerTestCount() * parseInt(process.env.TEST_PARALLEL_INDEX || '0');
const parallelIndex = parseInt(process.env.TEST_PARALLEL_INDEX || '0');

// NOTE: This assumes DEVICES_PER_TEST_COUNT=4 is set in CI for iOS (not applicable to Android)
// Worker pools are fixed at 4 devices each regardless of actual test size:
// Worker 0: devices 0-3, Worker 1: devices 4-7, Worker 2: devices 8-11
const devicesPerWorker = getDevicesPerTestCount();
const workerBaseOffset = devicesPerWorker * parallelIndex;

// Apply retry offset, but wrap within the worker's device pool only
// This means when retrying, alice/bob etc won't be the same device as before within a worker's pool
// This is to avoid any issues where a device might be in a bad state for some reason
// (e.g. not accessing photo library on iOS)
const retryOffset = testInfo.retry || 0;
const deviceIndexWithinWorker = (capabilitiesIndex + retryOffset) % devicesPerWorker;
const actualCapabilitiesIndex = workerBaseOffset + deviceIndexWithinWorker;

if (retryOffset > 0) {
console.info(
`Retry offset applied (#${retryOffset}), rotating device allocations within worker`
);
}

const opts: XCUITestDriverOpts = {
address: `http://localhost:${APPIUM_PORT}`,
Expand Down
Loading
Loading