From 3c57584d919ae880c72c3e6b78e0968adafa4baa Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Fri, 8 Aug 2025 17:17:34 +1000 Subject: [PATCH 01/30] refactor: no more 4 device tests --- .../specs/group_tests_add_contact.spec.ts | 20 +- .../specs/linked_device_create_group.spec.ts | 182 +++++++++--------- .../specs/linked_device_restore_group.spec.ts | 58 +++--- run/test/specs/linked_group_leave.spec.ts | 53 +++-- run/test/specs/state_builder/index.ts | 110 +++++++++++ 5 files changed, 263 insertions(+), 160 deletions(-) diff --git a/run/test/specs/group_tests_add_contact.spec.ts b/run/test/specs/group_tests_add_contact.spec.ts index 1981a6331..04a4d85be 100644 --- a/run/test/specs/group_tests_add_contact.spec.ts +++ b/run/test/specs/group_tests_add_contact.spec.ts @@ -8,7 +8,7 @@ import { ConversationSettings } from './locators/conversation'; import { Contact } from './locators/global'; import { InviteContactConfirm, ManageMembersMenuItem } from './locators/groups'; import { ConversationItem } from './locators/home'; -import { open_Alice1_Bob1_Charlie1_Unknown1 } from './state_builder'; +import { open_Alice1_Bob1_friends_group_Unknown1 } from './state_builder'; import { sleepFor } from './utils'; import { newUser } from './utils/create_account'; import { newContact } from './utils/create_contact'; @@ -18,22 +18,22 @@ bothPlatformsIt({ title: 'Add contact to group', risk: 'high', testCb: addContactToGroup, - countOfDevicesNeeded: 4, + countOfDevicesNeeded: 3, }); async function addContactToGroup(platform: SupportedPlatformsType, testInfo: TestInfo) { const testGroupName = 'Group to test adding contact'; const { - devices: { alice1, bob1, charlie1, unknown1 }, + devices: { alice1, bob1, unknown1 }, prebuilt: { alice, group }, - } = await open_Alice1_Bob1_Charlie1_Unknown1({ + } = await open_Alice1_Bob1_friends_group_Unknown1({ platform, groupName: testGroupName, focusGroupConvo: true, testInfo: testInfo, }); - const userD = await newUser(unknown1, USERNAME.DRACULA); + const userC = await newUser(unknown1, USERNAME.CHARLIE); await alice1.navigateBack(); - await newContact(platform, alice1, alice, unknown1, userD); + await newContact(platform, alice1, alice, unknown1, userC); // Exit to conversation list await alice1.navigateBack(); // Select group conversation in list @@ -49,7 +49,7 @@ async function addContactToGroup(platform: SupportedPlatformsType, testInfo: Tes // Select new user await alice1.clickOnElementAll({ ...new Contact(alice1).build(), - text: USERNAME.DRACULA, + text: USERNAME.CHARLIE, }); await alice1.clickOnElementAll(new InviteContactConfirm(alice1)); // Leave Manage Members @@ -58,9 +58,9 @@ async function addContactToGroup(platform: SupportedPlatformsType, testInfo: Tes await alice1.navigateBack(); // Check control messages await Promise.all( - [alice1, bob1, charlie1].map(device => + [alice1, bob1].map(device => device.waitForControlMessageToBePresent( - englishStrippedStr('groupMemberNew').withArgs({ name: USERNAME.DRACULA }).toString() + englishStrippedStr('groupMemberNew').withArgs({ name: USERNAME.CHARLIE }).toString() ) ) ); @@ -70,5 +70,5 @@ async function addContactToGroup(platform: SupportedPlatformsType, testInfo: Tes await unknown1.onAndroid().navigateBack(); await unknown1.clickOnElementAll(new ConversationItem(unknown1, group.groupName)); // Check for control message on device 4 await unknown1.waitForControlMessageToBePresent(englishStrippedStr('groupInviteYou').toString()); - await closeApp(alice1, bob1, charlie1, unknown1); + await closeApp(alice1, bob1, unknown1); } diff --git a/run/test/specs/linked_device_create_group.spec.ts b/run/test/specs/linked_device_create_group.spec.ts index 40899c26d..25acd1cb7 100644 --- a/run/test/specs/linked_device_create_group.spec.ts +++ b/run/test/specs/linked_device_create_group.spec.ts @@ -1,131 +1,123 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; -import { bothPlatformsItSeparate } from '../../types/sessionIt'; -import { USERNAME } from '../../types/testing'; +import { bothPlatformsIt } from '../../types/sessionIt'; import { ConversationHeaderName, ConversationSettings } from './locators/conversation'; import { EditGroupNameInput, SaveGroupNameChangeButton, UpdateGroupInformation, } from './locators/groups'; -import { ConversationItem } from './locators/home'; +import { open_Alice2_Bob1_friends_group } from './state_builder'; import { sleepFor } from './utils'; -import { newUser } from './utils/create_account'; -import { createGroup } from './utils/create_group'; -import { linkedDevice } from './utils/link_device'; -import { closeApp, openAppFourDevices, SupportedPlatformsType } from './utils/open_app'; +import { closeApp, SupportedPlatformsType } from './utils/open_app'; -bothPlatformsItSeparate({ - title: 'Create group and change name syncs', +bothPlatformsIt({ + title: 'Group name change syncs', risk: 'high', - countOfDevicesNeeded: 4, - ios: { - testCb: linkedGroupiOS, - }, - android: { - testCb: linkedGroupAndroid, - }, + countOfDevicesNeeded: 3, + testCb: linkedGroup, }); -async function linkedGroupiOS(platform: SupportedPlatformsType, testInfo: TestInfo) { - const { device1, device2, device3, device4 } = await openAppFourDevices(platform, testInfo); - const alice = await linkedDevice(device1, device2, USERNAME.ALICE); - const [bob, charlie] = await Promise.all([ - newUser(device3, USERNAME.BOB), - newUser(device4, USERNAME.CHARLIE), - ]); +async function linkedGroup(platform: SupportedPlatformsType, testInfo: TestInfo) { const testGroupName = 'Linked device group'; const newGroupName = 'New group name'; - // Note we keep this createGroup here as we want it to **indeed** use the UI to create the group - await createGroup(platform, device1, alice, device3, bob, device4, charlie, testGroupName); - // Test that group has loaded on linked device - await device2.clickOnElementAll(new ConversationItem(device2, testGroupName)); - // Change group name in device 1 - // Click on settings/more info - await device1.clickOnElementAll(new ConversationSettings(device1)); + const { + devices: { alice1, alice2, bob1 }, + } = await open_Alice2_Bob1_friends_group({ + platform, + groupName: testGroupName, + focusGroupConvo: true, + testInfo: testInfo, + }); + await alice1.clickOnElementAll(new ConversationSettings(alice1)); // Edit group await sleepFor(100); // click on group name to change it - await device1.clickOnElementAll(new UpdateGroupInformation(device1, testGroupName)); + await alice1.clickOnElementAll(new UpdateGroupInformation(alice1, testGroupName)); // Check new dialog - await device1.checkModalStrings( + await alice1.checkModalStrings( englishStrippedStr('updateGroupInformation').toString(), englishStrippedStr('updateGroupInformationDescription').toString() ); // Delete old name first - await device1.deleteText(new EditGroupNameInput(device1)); + await alice1.deleteText(new EditGroupNameInput(alice1)); // Type in new group name - await device1.inputText(newGroupName, new EditGroupNameInput(device1)); + await alice1.inputText(newGroupName, new EditGroupNameInput(alice1)); // Save changes - await device1.clickOnElementAll(new SaveGroupNameChangeButton(device1)); + await alice1.clickOnElementAll(new SaveGroupNameChangeButton(alice1)); // Go back to conversation - await device1.navigateBack(); + await alice1.navigateBack(); // Check control message for changed name const groupNameNew = englishStrippedStr('groupNameNew') .withArgs({ group_name: newGroupName }) .toString(); // Control message should be "Group name is now {group_name}." - await device1.waitForControlMessageToBePresent(groupNameNew); - // Wait 5 seconds for name to update - await sleepFor(5000); + await alice1.waitForControlMessageToBePresent(groupNameNew); // Check linked device for name change (conversation header name) - await device2.waitForTextElementToBePresent(new ConversationHeaderName(device2, newGroupName)); - await Promise.all([ - device2.waitForControlMessageToBePresent(groupNameNew), - device3.waitForControlMessageToBePresent(groupNameNew), - device4.waitForControlMessageToBePresent(groupNameNew), - ]); - await closeApp(device1, device2, device3, device4); + await alice2.waitForTextElementToBePresent( + new ConversationHeaderName(alice2, newGroupName) + ); + await Promise.all( + [alice1, alice2, bob1].map(device => + device.waitForTextElementToBePresent(new ConversationHeaderName(device, newGroupName)) + ) + ); + await Promise.all( + [alice2, bob1].map(device => device.waitForControlMessageToBePresent(groupNameNew)) + ); + await closeApp(alice1, alice2, bob1); } -async function linkedGroupAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { - const testGroupName = 'Test group'; - const newGroupName = 'Changed group name'; - const { device1, device2, device3, device4 } = await openAppFourDevices(platform, testInfo); - // Create users A, B and C - const alice = await linkedDevice(device1, device2, USERNAME.ALICE); - const [bob, charlie] = await Promise.all([ - newUser(device3, USERNAME.BOB), - newUser(device4, USERNAME.CHARLIE), - ]); - // Create group - // Note we keep this createGroup here as we want it to **indeed** use the UI to create the group - await createGroup(platform, device1, alice, device3, bob, device4, charlie, testGroupName); - // Test that group has loaded on linked device - await device2.clickOnElementAll(new ConversationItem(device2, testGroupName)); - // Click on settings or three dots - await device1.clickOnElementAll(new ConversationSettings(device1)); - // Click on Edit group option - await sleepFor(1000); - await device1.clickOnElementAll(new UpdateGroupInformation(device1)); - // Click on current group name - await device1.clickOnElementAll(new EditGroupNameInput(device1)); - // Remove current group name - await device1.deleteText(new EditGroupNameInput(device1)); - // Enter new group name (same test tag for both) - await device1.clickOnElementAll(new EditGroupNameInput(device1)); - await device1.inputText(newGroupName, new EditGroupNameInput(device1)); - // Click done/apply - await device1.clickOnElementAll(new SaveGroupNameChangeButton(device1)); - await device1.navigateBack(); - // Check control message for changed name - const groupNameNew = englishStrippedStr('groupNameNew') - .withArgs({ group_name: newGroupName }) - .toString(); - // Config message is "Group name is now {group_name}" - await device1.waitForControlMessageToBePresent(groupNameNew); - // Check linked device for name change (conversation header name) - await device2.waitForTextElementToBePresent(new ConversationHeaderName(device2, newGroupName)); - await Promise.all([ - device2.waitForControlMessageToBePresent(groupNameNew), - device3.waitForControlMessageToBePresent(groupNameNew), - device4.waitForControlMessageToBePresent(groupNameNew), - ]); - await closeApp(device1, device2, device3, device4); -} +// async function linkedGroupAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { +// const testGroupName = 'Test group'; +// const newGroupName = 'Changed group name'; +// const { device1, device2, device3, device4 } = await openAppFourDevices(platform, testInfo); +// // Create users A, B and C +// const alice = await linkedDevice(device1, device2, USERNAME.ALICE); +// const [bob, charlie] = await Promise.all([ +// newUser(device3, USERNAME.BOB), +// newUser(device4, USERNAME.CHARLIE), +// ]); +// // Create group +// // Note we keep this createGroup here as we want it to **indeed** use the UI to create the group +// await createGroup(platform, device1, alice, device3, bob, device4, charlie, testGroupName); +// // Test that group has loaded on linked device +// await device2.clickOnElementAll(new ConversationItem(device2, testGroupName)); +// // Click on settings or three dots +// await device1.clickOnElementAll(new ConversationSettings(device1)); +// // Click on Edit group option +// await sleepFor(1000); +// await device1.clickOnElementAll(new UpdateGroupInformation(device1)); +// // Click on current group name +// await device1.clickOnElementAll(new EditGroupNameInput(device1)); +// // Remove current group name +// await device1.deleteText(new EditGroupNameInput(device1)); +// // Enter new group name (same test tag for both) +// await device1.clickOnElementAll(new EditGroupNameInput(device1)); +// await device1.inputText(newGroupName, new EditGroupNameInput(device1)); +// // Click done/apply +// await device1.clickOnElementAll(new SaveGroupNameChangeButton(device1)); +// await device1.navigateBack(); +// // Check control message for changed name +// const groupNameNew = englishStrippedStr('groupNameNew') +// .withArgs({ group_name: newGroupName }) +// .toString(); +// // Config message is "Group name is now {group_name}" +// await device1.waitForControlMessageToBePresent(groupNameNew); +// // Check linked device for name change (conversation header name) +// await device2.waitForTextElementToBePresent( +// new ConversationHeaderName(device2).build(newGroupName) +// ); +// await Promise.all([ +// device2.waitForControlMessageToBePresent(groupNameNew), +// device3.waitForControlMessageToBePresent(groupNameNew), +// device4.waitForControlMessageToBePresent(groupNameNew), +// ]); +// await closeApp(device1, device2, device3, device4); +// } -// TODO -// Remove user -// Add user -// Disappearing messages +// // TODO +// // Remove user +// // Add user +// // Disappearing messages diff --git a/run/test/specs/linked_device_restore_group.spec.ts b/run/test/specs/linked_device_restore_group.spec.ts index 2c77a9faf..cff2f315b 100644 --- a/run/test/specs/linked_device_restore_group.spec.ts +++ b/run/test/specs/linked_device_restore_group.spec.ts @@ -4,47 +4,55 @@ import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; import { ConversationHeaderName, MessageBody } from './locators/conversation'; import { ConversationItem } from './locators/home'; -import { newUser } from './utils/create_account'; -import { createGroup } from './utils/create_group'; -import { closeApp, openAppFourDevices, SupportedPlatformsType } from './utils/open_app'; +import { open_Alice1_Bob1_friends_group_Unknown1 } from './state_builder'; +import { closeApp, SupportedPlatformsType } from './utils/open_app'; import { restoreAccount } from './utils/restore_account'; bothPlatformsIt({ title: 'Restore group', risk: 'high', testCb: restoreGroup, - countOfDevicesNeeded: 4, + countOfDevicesNeeded: 3, }); async function restoreGroup(platform: SupportedPlatformsType, testInfo: TestInfo) { - const testGroupName = 'Restore group'; - const { device1, device2, device3, device4 } = await openAppFourDevices(platform, testInfo); - const [alice, bob, charlie] = await Promise.all([ - newUser(device1, USERNAME.ALICE), - newUser(device2, USERNAME.BOB), - newUser(device3, USERNAME.CHARLIE), - ]); - await createGroup(platform, device1, alice, device2, bob, device3, charlie, testGroupName); - - const aliceMessage = `${USERNAME.ALICE} to ${testGroupName}`; - const bobMessage = `${USERNAME.BOB} to ${testGroupName}`; - const charlieMessage = `${USERNAME.CHARLIE} to ${testGroupName}`; - await restoreAccount(device4, alice); + const testGroupName = 'Group to test adding contact'; + const aliceMessage = 'Hello this is alice'; + const bobMessage = 'Hello this is bob'; + const { + devices: { alice1, bob1, unknown1 }, + prebuilt, + } = await open_Alice1_Bob1_friends_group_Unknown1({ + platform, + groupName: testGroupName, + focusGroupConvo: true, + testInfo: testInfo, + }); + const alice = { + userName: prebuilt.alice.userName, + accountID: prebuilt.alice.sessionId, + recoveryPhrase: prebuilt.alice.seedPhrase, + }; + await alice1.sendMessage(aliceMessage); + await bob1.sendMessage(bobMessage); + unknown1.setDeviceIdentity('alice2'); + await restoreAccount(unknown1, alice); // Check that group has loaded on linked device - await device4.clickOnElementAll(new ConversationItem(device4, testGroupName)); + await unknown1.clickOnElementAll(new ConversationItem(unknown1, testGroupName)); // Check the group name has loaded - await device4.waitForTextElementToBePresent(new ConversationHeaderName(device4, testGroupName)); + await unknown1.waitForTextElementToBePresent( + new ConversationHeaderName(unknown1, testGroupName) + ); // Check all messages are present await Promise.all([ - device4.waitForTextElementToBePresent(new MessageBody(device4, aliceMessage)), - device4.waitForTextElementToBePresent(new MessageBody(device4, bobMessage)), - device4.waitForTextElementToBePresent(new MessageBody(device4, charlieMessage)), + unknown1.waitForTextElementToBePresent(new MessageBody(unknown1, aliceMessage)), + unknown1.waitForTextElementToBePresent(new MessageBody(unknown1, bobMessage)), ]); const testMessage2 = 'Checking that message input is working'; - await device4.sendMessage(testMessage2); + await unknown1.sendMessage(testMessage2); await Promise.all( - [device1, device2, device3].map(device => + [alice1, bob1, unknown1].map(device => device.waitForTextElementToBePresent(new MessageBody(device, testMessage2)) ) ); - await closeApp(device1, device2, device3, device4); + await closeApp(alice1, bob1, unknown1, unknown1); } diff --git a/run/test/specs/linked_group_leave.spec.ts b/run/test/specs/linked_group_leave.spec.ts index 19a385504..8db664d93 100644 --- a/run/test/specs/linked_group_leave.spec.ts +++ b/run/test/specs/linked_group_leave.spec.ts @@ -2,53 +2,46 @@ import type { TestInfo } from '@playwright/test'; import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { bothPlatformsIt } from '../../types/sessionIt'; -import { USERNAME } from '../../types/testing'; import { ConversationSettings } from './locators/conversation'; import { LeaveGroupConfirm, LeaveGroupMenuItem } from './locators/groups'; +import { ConversationItem } from './locators/home'; +import { open_Alice2_Bob1_friends_group } from './state_builder'; import { sleepFor } from './utils'; -import { newUser } from './utils/create_account'; -import { createGroup } from './utils/create_group'; -import { linkedDevice } from './utils/link_device'; -import { closeApp, openAppFourDevices, SupportedPlatformsType } from './utils/open_app'; +import { closeApp, SupportedPlatformsType } from './utils/open_app'; bothPlatformsIt({ title: 'Leave group linked device', risk: 'high', testCb: leaveGroupLinkedDevice, - countOfDevicesNeeded: 4, + countOfDevicesNeeded: 3, }); async function leaveGroupLinkedDevice(platform: SupportedPlatformsType, testInfo: TestInfo) { - const testGroupName = 'Leave group linked device'; - const { device1, device2, device3, device4 } = await openAppFourDevices(platform, testInfo); - const charlie = await linkedDevice(device3, device4, USERNAME.CHARLIE); - // Create users A, B and C - const [alice, bob] = await Promise.all([ - newUser(device1, USERNAME.ALICE), - newUser(device2, USERNAME.BOB), - ]); - // Create group with user A, user B and User C - await createGroup(platform, device1, alice, device2, bob, device3, charlie, testGroupName); - await sleepFor(1000); - await device3.clickOnElementAll(new ConversationSettings(device3)); + const testGroupName = 'Linked device group'; + const { + devices: { alice1, alice2, bob1 }, + prebuilt: { bob }, + } = await open_Alice2_Bob1_friends_group({ + platform, + groupName: testGroupName, + focusGroupConvo: true, + testInfo: testInfo, + }); + await bob1.clickOnElementAll(new ConversationSettings(bob1)); await sleepFor(1000); - await device3.clickOnElementAll(new LeaveGroupMenuItem(device3)); - await device3.checkModalStrings( + await bob1.clickOnElementAll(new LeaveGroupMenuItem(bob1)); + await bob1.checkModalStrings( englishStrippedStr('groupLeave').toString(), englishStrippedStr('groupLeaveDescription').withArgs({ group_name: testGroupName }).toString() ); - // Modal with Leave/Cancel - await device3.clickOnElementAll(new LeaveGroupConfirm(device3)); - // Check for control message - await sleepFor(5000); - await device4.onIOS().hasTextElementBeenDeleted('Conversation list item', testGroupName); - // Create control message for user leaving group + await bob1.clickOnElementAll(new LeaveGroupConfirm(bob1)); + await bob1.verifyElementNotPresent(new ConversationItem(bob1, testGroupName)); const groupMemberLeft = englishStrippedStr('groupMemberLeft') - .withArgs({ name: charlie.userName }) + .withArgs({ name: bob.userName }) .toString(); await Promise.all([ - device1.waitForControlMessageToBePresent(groupMemberLeft), - device2.waitForControlMessageToBePresent(groupMemberLeft), + alice1.waitForControlMessageToBePresent(groupMemberLeft), + alice2.waitForControlMessageToBePresent(groupMemberLeft), ]); - await closeApp(device1, device2, device3, device4); + await closeApp(alice1, alice2, bob1); } diff --git a/run/test/specs/state_builder/index.ts b/run/test/specs/state_builder/index.ts index 885ddea05..9748900b0 100644 --- a/run/test/specs/state_builder/index.ts +++ b/run/test/specs/state_builder/index.ts @@ -203,6 +203,61 @@ export async function open_Alice1_Bob1_Charlie1_friends_group({ }; } +export async function open_Alice1_Bob1_friends_group_Unknown1({ + platform, + groupName, + focusGroupConvo = true, + testInfo, +}: WithPlatform & + WithFocusGroupConvo & { + groupName: string; + testInfo: TestInfo; + }) { + const stateToBuildKey = '2friendsInGroup'; + const appsToOpen = 3; + const result = await openAppsWithState({ + platform, + appsToOpen, + stateToBuildKey, + groupName, + testInfo, + }); + result.devices[0].setDeviceIdentity('alice1'); + result.devices[1].setDeviceIdentity('bob1'); + result.devices[2].setDeviceIdentity('unknown1'); // this device will be linked later + const seedPhrases = result.prebuilt.users.map(m => m.seedPhrase); + await linkDevices(result.devices.slice(0, -1), seedPhrases); + + const formattedGroup = { group: result.prebuilt.group }; + + const alice1 = result.devices[0]; + const bob1 = result.devices[1]; + + const formattedDevices = { + alice1, + bob1, + unknown1: result.devices[2], // not assigned yet + }; + const alice = result.prebuilt.users[0]; + const bob = result.prebuilt.users[1]; + const formattedUsers: WithUsers<2> = { + alice, + bob, + }; + if (focusGroupConvo) { + await focusConvoOnDevices({ + // slice off the last device as it will be used later (i.e. we don't want to link yet) + devices: result.devices.slice(0, -1), + convoName: result.prebuilt.group.groupName, + }); + } + + return { + devices: formattedDevices, + prebuilt: { ...formattedUsers, ...formattedGroup }, + }; +} + /** * Open 4 devices, one for Alice, one for Bob, one for Charlie, and one extra, unlinked. * This function is used for testing that we can do a bunch of actions without having a linked device, @@ -393,3 +448,58 @@ export async function open_Alice2_Bob1_friends({ prebuilt: { ...formattedUsers }, }; } + +export async function open_Alice2_Bob1_friends_group({ + platform, + groupName, + focusGroupConvo = true, + testInfo, +}: WithPlatform & + WithFocusGroupConvo & { + groupName: string; + testInfo: TestInfo; + }) { + const stateToBuildKey = '2friendsInGroup'; + const appsToOpen = 3; + const result = await openAppsWithState({ + platform, + appsToOpen, + stateToBuildKey, + groupName, + testInfo, + }); + result.devices[0].setDeviceIdentity('alice1'); + result.devices[1].setDeviceIdentity('alice2'); + result.devices[2].setDeviceIdentity('bob1'); + const alice = result.prebuilt.users[0]; + const bob = result.prebuilt.users[1]; + // we want the first user to have the first 2 devices linked + const seedPhrases = [alice.seedPhrase, alice.seedPhrase, bob.seedPhrase]; + await linkDevices(result.devices, seedPhrases); + + const alice1 = result.devices[0]; + const alice2 = result.devices[1]; + const bob1 = result.devices[2]; + + const formattedUsers: WithUsers<2> = { + alice, + bob, + }; + + if (focusGroupConvo) { + await focusConvoOnDevices({ + devices: result.devices, + convoName: result.prebuilt.group.groupName, + }); + } + + return { + devices: { + // alice has two devices linked right away + alice1, + alice2, + bob1, + }, + prebuilt: { ...formattedUsers }, + }; +} From a1c31ff60cd19de7f0f75e8d07c2f2efcb0bc545 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Fri, 29 Aug 2025 09:44:47 +1000 Subject: [PATCH 02/30] feat: add mac to setup script --- scripts/ci.sh | 103 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 32 deletions(-) diff --git a/scripts/ci.sh b/scripts/ci.sh index 647c403e1..1b1ba6d3d 100644 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -2,29 +2,47 @@ set -x # Android functions -ARCH="x86_64" +# Detect platform and set variables accordingly +if [[ "$OSTYPE" == "darwin"* ]]; then + # Mac settings + ARCH="arm64-v8a" + EMULATOR_COUNT=9 + API_LEVEL="35" + ANDROID_CMD="commandlinetools-mac-13114758_latest.zip" + EMULATOR_BIN="emulator" +else + # Linux settings + ARCH="x86_64" + EMULATOR_COUNT=4 + API_LEVEL="34" + ANDROID_CMD="commandlinetools-linux-13114758_latest.zip" + EMULATOR_BIN="emulator" +fi + +# Derive build tools version from API level +BUILD_TOOLS="${API_LEVEL}.0.0" TARGET="google_apis_playstore" -API_LEVEL="34" -BUILD_TOOLS="34.0.0" ANDROID_ARCH=${ANDROID_ARCH_DEFAULT} ANDROID_API_LEVEL="android-${API_LEVEL}" ANDROID_APIS="${TARGET};${ARCH}" EMULATOR_PACKAGE="system-images;${ANDROID_API_LEVEL};${ANDROID_APIS}" PLATFORM_VERSION="platforms;${ANDROID_API_LEVEL}" BUILD_TOOL="build-tools;${BUILD_TOOLS}" -ANDROID_CMD="commandlinetools-linux-11076708_latest.zip" export ANDROID_SDK_PACKAGES="${EMULATOR_PACKAGE} ${PLATFORM_VERSION} ${BUILD_TOOL} platform-tools" export ANDROID_SDK_ROOT=/opt/android -export PATH="$PATH:$ANDROID_SDK_ROOT/cmdline-tools/tools:$ANDROID_SDK_ROOT/cmdline-tools/tools/bin:$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/tools/bin:$ANDROID_SDK_ROOT/latform-tools:$ANDROID_SDK_ROOT/build-tools/${BUILD_TOOLS}:$ANDROID_SDK_ROOT/platform-tools/" +export PATH="$PATH:$ANDROID_SDK_ROOT/cmdline-tools/tools:$ANDROID_SDK_ROOT/cmdline-tools/tools/bin:$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/tools/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/build-tools/${BUILD_TOOLS}:$ANDROID_SDK_ROOT/platform-tools/" export EMULATOR_DEVICE="pixel_6" # all emulators are created with the pixel 6 spec for now # this should only be done when we bump the API version or add a worker to the CI that needs emulators to be setup # Once you've run this, you must also start_for_snapshots() and force_save_snapshots() (see details below) function create_emulators() { - sudo apt update - sudo apt install -y ca-certificates curl git vim bash wget unzip tree htop gzip default-jre libnss3 libxcursor1 libqt5gui5 libc++-dev libxcb-cursor0 htop tree tar gzip gh nload + # Skip apt install on Mac + if [[ "$OSTYPE" != "darwin"* ]]; then + sudo apt update + sudo apt install -y ca-certificates curl git vim bash wget unzip tree htop gzip default-jre libnss3 libxcursor1 libqt5gui5 libc++-dev libxcb-cursor0 htop tree tar gzip gh nload + fi sudo rm -rf $ANDROID_SDK_ROOT @@ -49,24 +67,31 @@ function create_emulators() { adb start-server - for i in {1..4} + for i in $(seq 1 $EMULATOR_COUNT) do echo "no" | avdmanager --verbose create avd --force --name "emulator$i" --device "${EMULATOR_DEVICE}" --package "${EMULATOR_PACKAGE}" - # Path to the AVD's config.ini file CONFIG_FILE="$HOME/.android/avd/emulator$i.avd/config.ini" - - # Set the RAM size to 4GB (4192MB) - sed -i 's/^hw\.ramSize=.*/hw.ramSize=4192/' "$CONFIG_FILE" - + + if [[ "$OSTYPE" == "darwin"* ]]; then + # Mac: 9 emulators @ 2GB each + sed -i '' 's/^hw\.ramSize=.*/hw.ramSize=2048/' "$CONFIG_FILE" + else + # Linux: Keep original 4GB + sed -i 's/^hw\.ramSize=.*/hw.ramSize=4192/' "$CONFIG_FILE" + fi done - - cd } function start_for_snapshots() { - for i in {1..4} + for i in $(seq 1 $EMULATOR_COUNT) do - DISPLAY=:0 emulator @emulator$i -gpu host -accel on -no-snapshot-load & + if [[ "$OSTYPE" == "darwin"* ]]; then + # Mac: Headless + $EMULATOR_BIN @emulator$i -no-window -no-snapshot-load & + else + # Linux: Keep as-is with display + DISPLAY=:0 $EMULATOR_BIN @emulator$i -gpu host -accel on -no-snapshot-load & + fi sleep 20 done } @@ -74,34 +99,48 @@ function start_for_snapshots() { # let the emulators start and be ready (check cpu usage) before calling this. # We want to take a snapshot woth emulators state as "done" as we can function force_save_snapshots() { - values=("5554" "5556" "5558" "5560") - for val in "${values[@]}" + # Dynamic port generation based on emulator count + for i in $(seq 1 $EMULATOR_COUNT) do - adb -s emulator-$val emu avd snapshot save plop.snapshot + port=$((5554 + (i-1)*2)) + adb -s emulator-$port emu avd snapshot save plop.snapshot done } function killall_emulators() { - killall qemu-system-x86_64; + if [[ "$OSTYPE" == "darwin"* ]]; then + killall qemu-system-aarch64 2>/dev/null || true + else + killall qemu-system-x86_64 2>/dev/null || true + fi } function start_with_snapshots() { - for i in {1..4}; do - EMU_CONFIG_FILE="$HOME/.android/avd/emulator$i.avd/emulator-user.ini" - # Set window position - sed -i "s/^window.x.*/window.x=$(( 100 + (i-1) * 400))/" "$EMU_CONFIG_FILE" - - DISPLAY=:0 emulator @emulator$i -gpu host -accel on -no-snapshot-save -snapshot plop.snapshot -force-snapshot-load & - - sleep 5 - done + for i in $(seq 1 $EMULATOR_COUNT); do + if [[ "$OSTYPE" == "darwin"* ]]; then + # Mac: Headless with 9 emulators + $EMULATOR_BIN @emulator$i \ + -no-window \ + -no-snapshot-save \ + -snapshot plop.snapshot \ + -force-snapshot-load & + else + # Linux: Keep as-is + EMU_CONFIG_FILE="$HOME/.android/avd/emulator$i.avd/emulator-user.ini" + sed -i "s/^window.x.*/window.x=$(( 100 + (i-1) * 400))/" "$EMU_CONFIG_FILE" + DISPLAY=:0 $EMULATOR_BIN @emulator$i -gpu host -accel on -no-snapshot-save -snapshot plop.snapshot -force-snapshot-load & + fi + + sleep 5 + done } function wait_for_emulators() { - for port in 5554 5556 5558 5560 + for i in $(seq 1 $EMULATOR_COUNT) do - for i in {1..60}; do + port=$((5554 + (i-1)*2)) + for j in {1..60}; do if adb -s emulator-$port shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then echo "emulator-$port booted" break From 0ae81ca38315988d1b40c2d8588cc7dc55996c68 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Fri, 29 Aug 2025 09:51:23 +1000 Subject: [PATCH 03/30] fix: macos specific commands, different apis --- scripts/ci.sh | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/scripts/ci.sh b/scripts/ci.sh index 1b1ba6d3d..37165f2fb 100644 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -10,6 +10,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then API_LEVEL="35" ANDROID_CMD="commandlinetools-mac-13114758_latest.zip" EMULATOR_BIN="emulator" + TARGET="google_apis" # Mac ARM doesn't have playstore variant else # Linux settings ARCH="x86_64" @@ -17,11 +18,12 @@ else API_LEVEL="34" ANDROID_CMD="commandlinetools-linux-13114758_latest.zip" EMULATOR_BIN="emulator" + TARGET="google_apis_playstore" # Linux can use playstore fi + # Derive build tools version from API level BUILD_TOOLS="${API_LEVEL}.0.0" -TARGET="google_apis_playstore" ANDROID_ARCH=${ANDROID_ARCH_DEFAULT} ANDROID_API_LEVEL="android-${API_LEVEL}" ANDROID_APIS="${TARGET};${ARCH}" @@ -47,12 +49,31 @@ function create_emulators() { sudo rm -rf $ANDROID_SDK_ROOT sudo mkdir -p $ANDROID_SDK_ROOT - sudo chown $USER:$USER $ANDROID_SDK_ROOT + if [[ "$OSTYPE" == "darwin"* ]]; then + sudo chown $USER:staff $ANDROID_SDK_ROOT + else + sudo chown $USER:$USER $ANDROID_SDK_ROOT + fi + # Download the SDK tools + if [[ "$OSTYPE" == "darwin"* ]]; then + curl -L -o /tmp/${ANDROID_CMD} https://dl.google.com/android/repository/${ANDROID_CMD} + else + wget https://dl.google.com/android/repository/${ANDROID_CMD} -P /tmp + fi + + # Check if download succeeded + if [[ ! -f /tmp/${ANDROID_CMD} ]]; then + echo "Failed to download Android SDK tools" + return 1 + fi - wget https://dl.google.com/android/repository/${ANDROID_CMD} -P /tmp && \ - unzip -d $ANDROID_SDK_ROOT /tmp/$ANDROID_CMD && \ - mkdir -p $ANDROID_SDK_ROOT/cmdline-tools/tools && cd $ANDROID_SDK_ROOT/cmdline-tools && mv NOTICE.txt source.properties bin lib tools/ && \ - cd $ANDROID_SDK_ROOT/cmdline-tools/tools && ls + # Extract and setup + unzip -d $ANDROID_SDK_ROOT /tmp/$ANDROID_CMD && \ + mkdir -p $ANDROID_SDK_ROOT/cmdline-tools/tools && \ + cd $ANDROID_SDK_ROOT/cmdline-tools && \ + mv NOTICE.txt source.properties bin lib tools/ && \ + cd $ANDROID_SDK_ROOT/cmdline-tools/tools && \ + ls yes Y | sdkmanager --licenses yes Y | sdkmanager --verbose --no_https ${ANDROID_SDK_PACKAGES} From dc087ec08f1c1607bb89e53c81a4b701da068c2d Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Fri, 29 Aug 2025 10:16:57 +1000 Subject: [PATCH 04/30] feat: let tests run on macos runner fix: killall script naming chore: debug path issues chore: homebrew android installation in path chore: set path in all steps chore: try to fix path issues chore: keep debugging android sdk install chore: hopefully last sdk fix chore: fix missing env vars chore: return android sdk root to env fix: actually allow 9 emulators fix: try 6 emulators only chore: 6 emulators for all 3 runs fix: try to download arm64 playstore build fix: correct number of workers for macos runner chore: try to run without snapshots for reliability fix: tests cannot run headless --- .github/workflows/android-regression.yml | 30 +++++--- run/test/specs/utils/binaries.ts | 78 ++++++++++++-------- run/test/specs/utils/capabilities_android.ts | 4 + scripts/ci.sh | 14 ++-- 4 files changed, 79 insertions(+), 47 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 66f9d15de..d57010a34 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -4,6 +4,14 @@ run-name: '${{ inputs.RISK }} regressions on ${{ github.head_ref || github.ref } on: workflow_dispatch: inputs: + RUNNER: + description: 'Which runner to use' + required: true + type: choice + options: + - 'linux' + - 'mac' + default: 'linux' APK_URL: description: 'apk url to test (.tar.xz)' required: true @@ -70,9 +78,10 @@ on: jobs: android-regression: - runs-on: [self-hosted, linux, X64, qa-android] + runs-on: ${{ github.event.inputs.RUNNER == 'mac' && fromJSON('["self-hosted", "macOS"]') || fromJSON('["self-hosted", "linux", "X64", "qa-android"]') }} env: PLATFORM: 'android' + EMULATOR_COUNT: ${{ github.event.inputs.RUNNER == 'mac' && '6' || '4' }} APK_URL: ${{ github.event.inputs.APK_URL }} BUILD_NUMBER: ${{ github.event.inputs.BUILD_NUMBER }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -80,8 +89,6 @@ jobs: ALLURE_ENABLED: ${{ github.event.inputs.ALLURE_ENABLED}} IOS_APP_PATH_PREFIX: '' ANDROID_APK: './extracted/session-android.apk' - APPIUM_ADB_FULL_PATH: '/opt/android/platform-tools/adb' - ANDROID_SDK_ROOT: '/opt/android' PLAYWRIGHT_RETRIES_COUNT: ${{ github.event.inputs.PLAYWRIGHT_RETRIES_COUNT }} PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.PRINT_FAILED_TEST_LOGS }} PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.PRINT_ONGOING_TEST_LOGS }} @@ -121,7 +128,11 @@ jobs: - name: Download APK & extract it run: | pwd - wget -q -O session-android.apk.tar.xz ${{ github.event.inputs.APK_URL }} + if [[ "${{ runner.os }}" == "macOS" ]]; then + curl -L -o session-android.apk.tar.xz ${{ github.event.inputs.APK_URL }} + else + wget -q -O session-android.apk.tar.xz ${{ github.event.inputs.APK_URL }} + fi tar xf session-android.apk.tar.xz mv session-android-*universal extracted @@ -144,6 +155,7 @@ jobs: continue-on-error: true # just so we don't fail if adb wasn't already running run: | source ./scripts/ci.sh + echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" >> $GITHUB_ENV adb kill-server; adb start-server; @@ -151,7 +163,7 @@ jobs: shell: bash run: | source ./scripts/ci.sh - start_with_snapshots + start_for_snapshots wait_for_emulators - name: List tests part of this run @@ -164,7 +176,7 @@ jobs: continue-on-error: true id: devices-1-test-run env: - PLAYWRIGHT_WORKERS_COUNT: 4 + PLAYWRIGHT_WORKERS_COUNT: ${{ env.EMULATOR_COUNT }} # 6 on mac, 4 on linux DEVICES_PER_TEST_COUNT: 1 run: | pwd @@ -180,7 +192,7 @@ jobs: continue-on-error: true id: devices-2-test-run env: - PLAYWRIGHT_WORKERS_COUNT: 2 + PLAYWRIGHT_WORKERS_COUNT: ${{ github.event.inputs.RUNNER == 'mac' && '3' || '2' }} DEVICES_PER_TEST_COUNT: 2 run: | pwd @@ -196,8 +208,8 @@ jobs: continue-on-error: true id: other-devices-test-run env: - PLAYWRIGHT_WORKERS_COUNT: 1 - DEVICES_PER_TEST_COUNT: 4 + PLAYWRIGHT_WORKERS_COUNT: ${{ github.event.inputs.RUNNER == 'mac' && '2' || '1' }} + DEVICES_PER_TEST_COUNT: 3 run: | pwd _TESTING=${{ github.event.inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test --grep "(?=.*@${PLATFORM})(?=.*@${{ github.event.inputs.RISK }})" --grep-invert "@1-devices|@2-devices" #Note: this has to be double quotes diff --git a/run/test/specs/utils/binaries.ts b/run/test/specs/utils/binaries.ts index a30b5adb3..1011df107 100644 --- a/run/test/specs/utils/binaries.ts +++ b/run/test/specs/utils/binaries.ts @@ -1,5 +1,6 @@ -import { existsSync, lstatSync } from 'fs'; +import { existsSync, lstatSync} from 'fs'; import { toNumber } from 'lodash'; +import * as path from 'path'; function existsAndFileOrThrow(path: string, id: string) { if (!existsSync(path) || !lstatSync(path).isFile()) { @@ -7,6 +8,14 @@ function existsAndFileOrThrow(path: string, id: string) { } } +const getAndroidSdkRoot = () => { + const sdkRoot = process.env.ANDROID_SDK_ROOT; + if (!sdkRoot) { + throw new Error('env variable `ANDROID_SDK_ROOT` needs to be set'); + } + return sdkRoot; +}; + export function getAndroidApk() { const fromEnv = process.env.ANDROID_APK; if (!fromEnv) { @@ -17,48 +26,53 @@ export function getAndroidApk() { } export const getAdbFullPath = () => { - const fromEnv = process.env.APPIUM_ADB_FULL_PATH; - if (!fromEnv) { - throw new Error('env variable `APPIUM_ADB_FULL_PATH` needs to be set'); - } - - existsAndFileOrThrow(fromEnv, 'adb'); - - return fromEnv; + const sdkRoot = getAndroidSdkRoot(); + const adbPath = path.join(sdkRoot, 'platform-tools', 'adb'); + existsAndFileOrThrow(adbPath, 'adb'); + return adbPath; }; export const getEmulatorFullPath = () => { - const fromEnv = process.env.EMULATOR_FULL_PATH; - - if (!fromEnv) { - throw new Error('env variable `EMULATOR_FULL_PATH` needs to be set'); - } - existsAndFileOrThrow(fromEnv, 'EMULATOR_FULL_PATH'); - - return fromEnv; + const sdkRoot = getAndroidSdkRoot(); + const emulatorPath = path.join(sdkRoot, 'emulator', 'emulator'); + existsAndFileOrThrow(emulatorPath, 'emulator'); + return emulatorPath; }; export const getAvdManagerFullPath = () => { - const fromEnv = process.env.AVD_MANAGER_FULL_PATH; - - if (!fromEnv) { - throw new Error('env variable `AVD_MANAGER_FULL_PATH` needs to be set'); + const sdkRoot = getAndroidSdkRoot(); + // Try multiple possible locations + const possiblePaths = [ + path.join(sdkRoot, 'cmdline-tools', 'latest', 'bin', 'avdmanager'), + path.join(sdkRoot, 'cmdline-tools', 'tools', 'bin', 'avdmanager'), + path.join(sdkRoot, 'tools', 'bin', 'avdmanager') + ]; + + for (const path of possiblePaths) { + if (existsSync(path) && lstatSync(path).isFile()) { + return path; + } } - existsAndFileOrThrow(fromEnv, 'AVD_MANAGER_FULL_PATH'); - - return fromEnv; + + throw new Error(`avdmanager not found in any expected location under ${sdkRoot}`); }; export const getSdkManagerFullPath = () => { - const fromEnv = process.env.SDK_MANAGER_FULL_PATH; - - if (!fromEnv) { - throw new Error('env variable `SDK_MANAGER_FULL_PATH` needs to be set'); + const sdkRoot = getAndroidSdkRoot(); + // Try multiple possible locations + const possiblePaths = [ + path.join(sdkRoot, 'cmdline-tools', 'latest', 'bin', 'sdkmanager'), + path.join(sdkRoot, 'cmdline-tools', 'tools', 'bin', 'sdkmanager'), + path.join(sdkRoot, 'tools', 'bin', 'sdkmanager') + ]; + + for (const path of possiblePaths) { + if (existsSync(path) && lstatSync(path).isFile()) { + return path; + } } - - existsAndFileOrThrow(fromEnv, 'SDK_MANAGER_FULL_PATH'); - - return fromEnv; + + throw new Error(`sdkmanager not found in any expected location under ${sdkRoot}`); }; export const getAndroidSystemImageToUse = () => { diff --git a/run/test/specs/utils/capabilities_android.ts b/run/test/specs/utils/capabilities_android.ts index 3bb752fb8..c0ca7fd06 100644 --- a/run/test/specs/utils/capabilities_android.ts +++ b/run/test/specs/utils/capabilities_android.ts @@ -35,6 +35,7 @@ const emulator5Udid = 'emulator-5562'; const emulator6Udid = 'emulator-5564'; const emulator7Udid = 'emulator-5566'; const emulator8Udid = 'emulator-5568'; +const emulator9Udid = 'emulator-5570'; const udids = [ emulator1Udid, @@ -45,6 +46,7 @@ const udids = [ emulator6Udid, emulator7Udid, emulator8Udid, + emulator9Udid, ]; const emulatorCapabilities: AppiumCapabilities[] = udids.map(udid => ({ @@ -61,6 +63,7 @@ const emulatorCapabilities5 = emulatorCapabilities[4]; const emulatorCapabilities6 = emulatorCapabilities[5]; const emulatorCapabilities7 = emulatorCapabilities[6]; const emulatorCapabilities8 = emulatorCapabilities[7]; +const emulatorCapabilities9 = emulatorCapabilities[8]; export const androidCapabilities = { sharedCapabilities, @@ -77,6 +80,7 @@ function getAllCaps() { emulatorCapabilities6, emulatorCapabilities7, emulatorCapabilities8, + emulatorCapabilities9, ]; return emulatorCaps; } diff --git a/scripts/ci.sh b/scripts/ci.sh index 37165f2fb..4b293cf02 100644 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -6,11 +6,12 @@ set -x if [[ "$OSTYPE" == "darwin"* ]]; then # Mac settings ARCH="arm64-v8a" - EMULATOR_COUNT=9 + EMULATOR_COUNT=6 API_LEVEL="35" ANDROID_CMD="commandlinetools-mac-13114758_latest.zip" EMULATOR_BIN="emulator" - TARGET="google_apis" # Mac ARM doesn't have playstore variant + TARGET="google_apis_playstore" # Mac ARM doesn't have playstore variant + ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT:-"$HOME/Android/sdk"} else # Linux settings ARCH="x86_64" @@ -19,6 +20,7 @@ else ANDROID_CMD="commandlinetools-linux-13114758_latest.zip" EMULATOR_BIN="emulator" TARGET="google_apis_playstore" # Linux can use playstore + ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT:-"/opt/android"} fi @@ -31,7 +33,7 @@ EMULATOR_PACKAGE="system-images;${ANDROID_API_LEVEL};${ANDROID_APIS}" PLATFORM_VERSION="platforms;${ANDROID_API_LEVEL}" BUILD_TOOL="build-tools;${BUILD_TOOLS}" export ANDROID_SDK_PACKAGES="${EMULATOR_PACKAGE} ${PLATFORM_VERSION} ${BUILD_TOOL} platform-tools" -export ANDROID_SDK_ROOT=/opt/android +export ANDROID_SDK_ROOT export PATH="$PATH:$ANDROID_SDK_ROOT/cmdline-tools/tools:$ANDROID_SDK_ROOT/cmdline-tools/tools/bin:$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/tools/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/build-tools/${BUILD_TOOLS}:$ANDROID_SDK_ROOT/platform-tools/" export EMULATOR_DEVICE="pixel_6" # all emulators are created with the pixel 6 spec for now @@ -107,8 +109,8 @@ function start_for_snapshots() { for i in $(seq 1 $EMULATOR_COUNT) do if [[ "$OSTYPE" == "darwin"* ]]; then - # Mac: Headless - $EMULATOR_BIN @emulator$i -no-window -no-snapshot-load & + # Mac: Not headless + $EMULATOR_BIN @emulator$i -no-snapshot-load & else # Linux: Keep as-is with display DISPLAY=:0 $EMULATOR_BIN @emulator$i -gpu host -accel on -no-snapshot-load & @@ -130,7 +132,7 @@ function force_save_snapshots() { function killall_emulators() { if [[ "$OSTYPE" == "darwin"* ]]; then - killall qemu-system-aarch64 2>/dev/null || true + killall qemu-system-aarch64-headless 2>/dev/null || true else killall qemu-system-x86_64 2>/dev/null || true fi From 2e2593e21640eea843344d78f3ada8a1c7beaba0 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Fri, 29 Aug 2025 12:02:47 +1000 Subject: [PATCH 05/30] feat: bump to node 20 to resolve fetch issues --- .nvmrc | 2 +- yarn.lock | 910 ++++++++++++++++++++++++++---------------------------- 2 files changed, 435 insertions(+), 477 deletions(-) diff --git a/.nvmrc b/.nvmrc index 55bffd620..6263619f7 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.15.0 +20.18.2 \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 82c08a783..085930c0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -266,12 +266,12 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:^1.4.3": - version: 1.4.3 - resolution: "@emnapi/runtime@npm:1.4.3" +"@emnapi/runtime@npm:^1.4.4": + version: 1.4.5 + resolution: "@emnapi/runtime@npm:1.4.5" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/3b7ab72d21cb4e034f07df80165265f85f445ef3f581d1bc87b67e5239428baa00200b68a7d5e37a0425c3a78320b541b07f76c5530f6f6f95336a6294ebf30b + checksum: 10c0/37a0278be5ac81e918efe36f1449875cbafba947039c53c65a1f8fc238001b866446fc66041513b286baaff5d6f9bec667f5164b3ca481373a8d9cb65bfc984b languageName: node linkType: hard @@ -293,39 +293,30 @@ __metadata: languageName: node linkType: hard -"@eslint/config-array@npm:^0.20.1": - version: 0.20.1 - resolution: "@eslint/config-array@npm:0.20.1" +"@eslint/config-array@npm:^0.21.0": + version: 0.21.0 + resolution: "@eslint/config-array@npm:0.21.0" dependencies: "@eslint/object-schema": "npm:^2.1.6" debug: "npm:^4.3.1" minimatch: "npm:^3.1.2" - checksum: 10c0/709108c3925d83c2166024646829ab61ba5fa85c6568daefd32508899f46ed8dc36d7153042df6dcc7e58ad543bc93298b646575daecb5eb4e39a43d838dab42 - languageName: node - linkType: hard - -"@eslint/config-helpers@npm:^0.2.1": - version: 0.2.3 - resolution: "@eslint/config-helpers@npm:0.2.3" - checksum: 10c0/8fd36d7f33013628787947c81894807c7498b31eacf6648efa6d7c7a99aac6bf0d59a8a4d14f968ec2aeebefb76a1a7e4fd4cd556a296323d4711b3d7a7cda22 + checksum: 10c0/0ea801139166c4aa56465b309af512ef9b2d3c68f9198751bbc3e21894fe70f25fbf26e1b0e9fffff41857bc21bfddeee58649ae6d79aadcd747db0c5dca771f languageName: node linkType: hard -"@eslint/core@npm:^0.14.0": - version: 0.14.0 - resolution: "@eslint/core@npm:0.14.0" - dependencies: - "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/259f279445834ba2d2cbcc18e9d43202a4011fde22f29d5fb802181d66e0f6f0bd1f6b4b4b46663451f545d35134498231bd5e656e18d9034a457824b92b7741 +"@eslint/config-helpers@npm:^0.3.1": + version: 0.3.1 + resolution: "@eslint/config-helpers@npm:0.3.1" + checksum: 10c0/f6c5b3a0b76a0d7d84cc93e310c259e6c3e0792ddd0a62c5fc0027796ffae44183432cb74b2c2b1162801ee1b1b34a6beb5d90a151632b4df7349f994146a856 languageName: node linkType: hard -"@eslint/core@npm:^0.15.0": - version: 0.15.0 - resolution: "@eslint/core@npm:0.15.0" +"@eslint/core@npm:^0.15.2": + version: 0.15.2 + resolution: "@eslint/core@npm:0.15.2" dependencies: "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/9882c69acfe29743ce473a619d5248589c6687561afaabe8ec8d7ffed07592db16edcca3af022f33ea92fe5f6cfbe3545ee53e89292579d22a944ebaeddcf72d + checksum: 10c0/c17a6dc4f5a6006ecb60165cc38bcd21fefb4a10c7a2578a0cfe5813bbd442531a87ed741da5adab5eb678e8e693fda2e2b14555b035355537e32bcec367ea17 languageName: node linkType: hard @@ -346,10 +337,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.29.0, @eslint/js@npm:^9.14.0": - version: 9.29.0 - resolution: "@eslint/js@npm:9.29.0" - checksum: 10c0/d0ccf37063fa27a3fae9347cb044f84ca10b5a2fa19ffb2b3fedf3b96843ac1ff359ea9f0ab0e80f2f16fda4cb0dc61ea0fed0375090f050fe0a029e7d6de3a3 +"@eslint/js@npm:9.34.0, @eslint/js@npm:^9.14.0": + version: 9.34.0 + resolution: "@eslint/js@npm:9.34.0" + checksum: 10c0/53f1bfd2a374683d9382a6850354555f6e89a88416c34a5d34e9fbbaf717e97c2b06300e8f93e5eddba8bda8951ccab7f93a680e56ded1a3d21d526019e69bab languageName: node linkType: hard @@ -360,13 +351,13 @@ __metadata: languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.3.1": - version: 0.3.2 - resolution: "@eslint/plugin-kit@npm:0.3.2" +"@eslint/plugin-kit@npm:^0.3.5": + version: 0.3.5 + resolution: "@eslint/plugin-kit@npm:0.3.5" dependencies: - "@eslint/core": "npm:^0.15.0" + "@eslint/core": "npm:^0.15.2" levn: "npm:^0.4.1" - checksum: 10c0/e069b0a46eb9fa595a1ac7dea4540a9daa493afba88875ee054e9117609c1c41555e779303cb4cff36cf88f603ba6eba2556a927e8ced77002828206ee17fc7e + checksum: 10c0/c178c1b58c574200c0fd125af3e4bc775daba7ce434ba6d1eeaf9bcb64b2e9fea75efabffb3ed3ab28858e55a016a5efa95f509994ee4341b341199ca630b89e languageName: node linkType: hard @@ -408,11 +399,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-arm64@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-darwin-arm64@npm:0.34.2" +"@img/sharp-darwin-arm64@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-darwin-arm64@npm:0.34.3" dependencies: - "@img/sharp-libvips-darwin-arm64": "npm:1.1.0" + "@img/sharp-libvips-darwin-arm64": "npm:1.2.0" dependenciesMeta: "@img/sharp-libvips-darwin-arm64": optional: true @@ -420,11 +411,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-x64@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-darwin-x64@npm:0.34.2" +"@img/sharp-darwin-x64@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-darwin-x64@npm:0.34.3" dependencies: - "@img/sharp-libvips-darwin-x64": "npm:1.1.0" + "@img/sharp-libvips-darwin-x64": "npm:1.2.0" dependenciesMeta: "@img/sharp-libvips-darwin-x64": optional: true @@ -432,74 +423,74 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-darwin-arm64@npm:1.1.0": - version: 1.1.0 - resolution: "@img/sharp-libvips-darwin-arm64@npm:1.1.0" +"@img/sharp-libvips-darwin-arm64@npm:1.2.0": + version: 1.2.0 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@img/sharp-libvips-darwin-x64@npm:1.1.0": - version: 1.1.0 - resolution: "@img/sharp-libvips-darwin-x64@npm:1.1.0" +"@img/sharp-libvips-darwin-x64@npm:1.2.0": + version: 1.2.0 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@img/sharp-libvips-linux-arm64@npm:1.1.0": - version: 1.1.0 - resolution: "@img/sharp-libvips-linux-arm64@npm:1.1.0" +"@img/sharp-libvips-linux-arm64@npm:1.2.0": + version: 1.2.0 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-arm@npm:1.1.0": - version: 1.1.0 - resolution: "@img/sharp-libvips-linux-arm@npm:1.1.0" +"@img/sharp-libvips-linux-arm@npm:1.2.0": + version: 1.2.0 + resolution: "@img/sharp-libvips-linux-arm@npm:1.2.0" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-ppc64@npm:1.1.0": - version: 1.1.0 - resolution: "@img/sharp-libvips-linux-ppc64@npm:1.1.0" +"@img/sharp-libvips-linux-ppc64@npm:1.2.0": + version: 1.2.0 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.0" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-s390x@npm:1.1.0": - version: 1.1.0 - resolution: "@img/sharp-libvips-linux-s390x@npm:1.1.0" +"@img/sharp-libvips-linux-s390x@npm:1.2.0": + version: 1.2.0 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.0" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linux-x64@npm:1.1.0": - version: 1.1.0 - resolution: "@img/sharp-libvips-linux-x64@npm:1.1.0" +"@img/sharp-libvips-linux-x64@npm:1.2.0": + version: 1.2.0 + resolution: "@img/sharp-libvips-linux-x64@npm:1.2.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0": - version: 1.1.0 - resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0" +"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0": + version: 1.2.0 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-x64@npm:1.1.0": - version: 1.1.0 - resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.1.0" +"@img/sharp-libvips-linuxmusl-x64@npm:1.2.0": + version: 1.2.0 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@img/sharp-linux-arm64@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-linux-arm64@npm:0.34.2" +"@img/sharp-linux-arm64@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-linux-arm64@npm:0.34.3" dependencies: - "@img/sharp-libvips-linux-arm64": "npm:1.1.0" + "@img/sharp-libvips-linux-arm64": "npm:1.2.0" dependenciesMeta: "@img/sharp-libvips-linux-arm64": optional: true @@ -507,11 +498,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-arm@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-linux-arm@npm:0.34.2" +"@img/sharp-linux-arm@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-linux-arm@npm:0.34.3" dependencies: - "@img/sharp-libvips-linux-arm": "npm:1.1.0" + "@img/sharp-libvips-linux-arm": "npm:1.2.0" dependenciesMeta: "@img/sharp-libvips-linux-arm": optional: true @@ -519,11 +510,23 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-s390x@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-linux-s390x@npm:0.34.2" +"@img/sharp-linux-ppc64@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-linux-ppc64@npm:0.34.3" + dependencies: + "@img/sharp-libvips-linux-ppc64": "npm:1.2.0" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-linux-s390x@npm:0.34.3" dependencies: - "@img/sharp-libvips-linux-s390x": "npm:1.1.0" + "@img/sharp-libvips-linux-s390x": "npm:1.2.0" dependenciesMeta: "@img/sharp-libvips-linux-s390x": optional: true @@ -531,11 +534,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-x64@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-linux-x64@npm:0.34.2" +"@img/sharp-linux-x64@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-linux-x64@npm:0.34.3" dependencies: - "@img/sharp-libvips-linux-x64": "npm:1.1.0" + "@img/sharp-libvips-linux-x64": "npm:1.2.0" dependenciesMeta: "@img/sharp-libvips-linux-x64": optional: true @@ -543,11 +546,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-arm64@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.2" +"@img/sharp-linuxmusl-arm64@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.3" dependencies: - "@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0" dependenciesMeta: "@img/sharp-libvips-linuxmusl-arm64": optional: true @@ -555,11 +558,11 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-x64@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-linuxmusl-x64@npm:0.34.2" +"@img/sharp-linuxmusl-x64@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.3" dependencies: - "@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0" dependenciesMeta: "@img/sharp-libvips-linuxmusl-x64": optional: true @@ -567,32 +570,32 @@ __metadata: languageName: node linkType: hard -"@img/sharp-wasm32@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-wasm32@npm:0.34.2" +"@img/sharp-wasm32@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-wasm32@npm:0.34.3" dependencies: - "@emnapi/runtime": "npm:^1.4.3" + "@emnapi/runtime": "npm:^1.4.4" conditions: cpu=wasm32 languageName: node linkType: hard -"@img/sharp-win32-arm64@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-win32-arm64@npm:0.34.2" +"@img/sharp-win32-arm64@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-win32-arm64@npm:0.34.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@img/sharp-win32-ia32@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-win32-ia32@npm:0.34.2" +"@img/sharp-win32-ia32@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-win32-ia32@npm:0.34.3" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@img/sharp-win32-x64@npm:0.34.2": - version: 0.34.2 - resolution: "@img/sharp-win32-x64@npm:0.34.2" +"@img/sharp-win32-x64@npm:0.34.3": + version: 0.34.3 + resolution: "@img/sharp-win32-x64@npm:0.34.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -644,9 +647,9 @@ __metadata: linkType: hard "@jridgewell/sourcemap-codec@npm:^1.4.10": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 languageName: node linkType: hard @@ -758,13 +761,13 @@ __metadata: linkType: hard "@playwright/test@npm:^1.45.1": - version: 1.53.1 - resolution: "@playwright/test@npm:1.53.1" + version: 1.55.0 + resolution: "@playwright/test@npm:1.55.0" dependencies: - playwright: "npm:1.53.1" + playwright: "npm:1.55.0" bin: playwright: cli.js - checksum: 10c0/f2ef7899ca6bc178c9f2ba6c8633d0bbeb5ba135048e9df01ecb949c6f743811ba5ef76d495f619454074c81dbfa28be12e4c0a1224bb2af0d4cb403182c716f + checksum: 10c0/e68b59cd8271f1b57c0649fc0562ab2d5f6bba8c3653dd7bd52ca1338dc380fde34588d0254e3cd3f0f2b20af04a80dfb080419ceb7475990bb2fc4d8c474984 languageName: node linkType: hard @@ -889,13 +892,12 @@ __metadata: linkType: hard "@sinonjs/samsam@npm:^8.0.1": - version: 8.0.2 - resolution: "@sinonjs/samsam@npm:8.0.2" + version: 8.0.3 + resolution: "@sinonjs/samsam@npm:8.0.3" dependencies: "@sinonjs/commons": "npm:^3.0.1" - lodash.get: "npm:^4.4.2" type-detect: "npm:^4.1.0" - checksum: 10c0/31d74c415040161f2963a202d7f866bedbb5a9b522a74b08a17086c15a75c3ef2893eecebb0c65a7b1603ef4ebdf83fa73cbe384b4cd679944918ed833200443 + checksum: 10c0/9bf57a8f8a484b3455696786e1679db7f0d6017de62099ee304bd364281fcb20895b7c6b05292aa10fecf76df27691e914fc3e1cb8a56d88c027e87d869dcf0c languageName: node linkType: hard @@ -1026,36 +1028,36 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.191, @types/lodash@npm:^4.14.192": - version: 4.17.18 - resolution: "@types/lodash@npm:4.17.18" - checksum: 10c0/d25f86941990403d59dcaae42b42fc9fef55a0a7f398790517e7189300183e425a94127594cbeaf9b5fcdc4f6c2b285e34f4301fb56c92f81ccd8505a41ab5f5 + version: 4.17.20 + resolution: "@types/lodash@npm:4.17.20" + checksum: 10c0/98cdd0faae22cbb8079a01a3bb65aa8f8c41143367486c1cbf5adc83f16c9272a2a5d2c1f541f61d0d73da543c16ee1d21cf2ef86cb93cd0cc0ac3bced6dd88f languageName: node linkType: hard "@types/node@npm:*": - version: 24.0.3 - resolution: "@types/node@npm:24.0.3" + version: 24.3.0 + resolution: "@types/node@npm:24.3.0" dependencies: - undici-types: "npm:~7.8.0" - checksum: 10c0/9c3c4e87600d1cf11e291c2fd4bfd806a615455463c30a0ef6dc9c801b3423344d9b82b8084e3ccabce485a7421ebb61a66e9676181bd7d9aea4759998a120d5 + undici-types: "npm:~7.10.0" + checksum: 10c0/96bdeca01f690338957c2dcc92cb9f76c262c10398f8d91860865464412b0f9d309c24d9b03d0bdd26dd47fa7ee3f8227893d5c89bc2009d919a525a22512030 languageName: node linkType: hard "@types/node@npm:^20.14.10": - version: 20.19.1 - resolution: "@types/node@npm:20.19.1" + version: 20.19.11 + resolution: "@types/node@npm:20.19.11" dependencies: undici-types: "npm:~6.21.0" - checksum: 10c0/4f1c3c8ec24c79af6802b376fa307904abf19accb9ac291de0bfc02220494c8b027d3ef733dbf64cc09b37594f22f679a15eabb30f3785bcfcc13bd9bbd8c0e2 + checksum: 10c0/9eecc4be04f1a8afbb8f8059b322fd0bbceeb02f96669bbaa52fb0b264c2e3269432a8833ada4be7b335e18d6b438b2d2c0274f5b3f54cc2081cb7c5374a6561 languageName: node linkType: hard "@types/node@npm:^22.2.0": - version: 22.15.32 - resolution: "@types/node@npm:22.15.32" + version: 22.18.0 + resolution: "@types/node@npm:22.18.0" dependencies: undici-types: "npm:~6.21.0" - checksum: 10c0/63a2fa52adf1134d1b3bee8b1862d4b8e4550fffc190551068d3d41a41d9e5c0c8f1cb81faa18767b260637360f662115c26c5e4e7718868ead40c4a57cbc0e3 + checksum: 10c0/02cce4493eee8408e66e76fcad164f33c0600ed0854ad08e5519a76a06402da5b589b278cf71bc975c9e014f2668bdf758bc3be7fed63bdbfd0900495372797c languageName: node linkType: hard @@ -1130,24 +1132,24 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.34.1" +"@typescript-eslint/eslint-plugin@npm:8.41.0": + version: 8.41.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.41.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.34.1" - "@typescript-eslint/type-utils": "npm:8.34.1" - "@typescript-eslint/utils": "npm:8.34.1" - "@typescript-eslint/visitor-keys": "npm:8.34.1" + "@typescript-eslint/scope-manager": "npm:8.41.0" + "@typescript-eslint/type-utils": "npm:8.41.0" + "@typescript-eslint/utils": "npm:8.41.0" + "@typescript-eslint/visitor-keys": "npm:8.41.0" graphemer: "npm:^1.4.0" ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.1.0" peerDependencies: - "@typescript-eslint/parser": ^8.34.1 + "@typescript-eslint/parser": ^8.41.0 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/f1c9f25e4fe4b59622312dfa0ca1e80fa7945296ba5c04362a5fda084a17e23a6b98dac331f5a13bcb1ba34a2b598a3f5c41aa288f0c51fe60196e912954e56a + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/29812ee5deeae65e67db29faa8d96bc70255c45788f342b11838850ea29a96e4331622cad3e703ffacaa895372845d44fd6b04786117c78f1a027595adff2e62 languageName: node linkType: hard @@ -1175,19 +1177,19 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/parser@npm:8.34.1" +"@typescript-eslint/parser@npm:8.41.0": + version: 8.41.0 + resolution: "@typescript-eslint/parser@npm:8.41.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.34.1" - "@typescript-eslint/types": "npm:8.34.1" - "@typescript-eslint/typescript-estree": "npm:8.34.1" - "@typescript-eslint/visitor-keys": "npm:8.34.1" + "@typescript-eslint/scope-manager": "npm:8.41.0" + "@typescript-eslint/types": "npm:8.41.0" + "@typescript-eslint/typescript-estree": "npm:8.41.0" + "@typescript-eslint/visitor-keys": "npm:8.41.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/bf8070245d53ef6926ff6630bb72f245923f545304e2a61508fb944802a83fed8eab961d9010956d07999d51afdfbbec82aea9d6185295551a7c17c00d759183 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/ca13ff505e9253aee761741f96714cd65a296bbfcac961efbbf7a909ff3d180b2142a23db0a2a5e50b928fa56586528b7e47ba6301089dd850945018dbf2ef50 languageName: node linkType: hard @@ -1208,29 +1210,16 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/project-service@npm:8.34.1" - dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.34.1" - "@typescript-eslint/types": "npm:^8.34.1" - debug: "npm:^4.3.4" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/9333a890625f6777054db17a6b299281ae7502bb7615261d15b885a75b8cf65fc91591389c93b37ecd14b651d8e94851dac8718e5dcc8ed0600533535dae855c - languageName: node - linkType: hard - -"@typescript-eslint/project-service@npm:8.35.1": - version: 8.35.1 - resolution: "@typescript-eslint/project-service@npm:8.35.1" +"@typescript-eslint/project-service@npm:8.41.0": + version: 8.41.0 + resolution: "@typescript-eslint/project-service@npm:8.41.0" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.35.1" - "@typescript-eslint/types": "npm:^8.35.1" + "@typescript-eslint/tsconfig-utils": "npm:^8.41.0" + "@typescript-eslint/types": "npm:^8.41.0" debug: "npm:^4.3.4" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/f8e88d773d7e9f193a05b4daeca1e7571fa0059b36ffad291fc6d83c9df94fbe38c935e076ae29e755bcb6008c4ee5c1073ebb2077258c5c0b53c76a23eb3c16 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/907ba880fcaf0805fc97012b431536b5b06db6ae4a0095708f9d9a4406feddabd964f09ea4ca99d8fa7bd141dbcc9496f1a9eb6683361a6bb01fb714a361126c languageName: node linkType: hard @@ -1244,41 +1233,22 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/scope-manager@npm:8.34.1" - dependencies: - "@typescript-eslint/types": "npm:8.34.1" - "@typescript-eslint/visitor-keys": "npm:8.34.1" - checksum: 10c0/2af608fa3900f4726322e33bf4f3a376fdace3ac0f310cf7d9256bbc2905c3896138176a47dd195d2c2229f27fe43f5deb4bc7729db2eb18389926dedea78077 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:8.35.1": - version: 8.35.1 - resolution: "@typescript-eslint/scope-manager@npm:8.35.1" +"@typescript-eslint/scope-manager@npm:8.41.0": + version: 8.41.0 + resolution: "@typescript-eslint/scope-manager@npm:8.41.0" dependencies: - "@typescript-eslint/types": "npm:8.35.1" - "@typescript-eslint/visitor-keys": "npm:8.35.1" - checksum: 10c0/ddfa0b81f47402874efcdd8e0857142600d90fc4e827243ed0fd058731e77e4beb8f5a60425117d1d4146d84437f538cf303f7bfebbd0f02733b202aa30a8393 + "@typescript-eslint/types": "npm:8.41.0" + "@typescript-eslint/visitor-keys": "npm:8.41.0" + checksum: 10c0/6b339ac1fc37a1e05dc6de421db9f9b138c357497ec87af2471ad30e48c78b4979d3da40943a1c81fc85d1537326a4f938843434db63d29eff414b9364daf8e8 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.34.1, @typescript-eslint/tsconfig-utils@npm:^8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.34.1" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/8d1ead8b7c279b48e2ed96f083ec119a9aeea1ca9cdd40576ec271b996b9fd8cfa0ddb0aafbb4e14bc27fc62c69c5be66d39b1de68eab9ddd7f1861da267423d - languageName: node - linkType: hard - -"@typescript-eslint/tsconfig-utils@npm:8.35.1, @typescript-eslint/tsconfig-utils@npm:^8.35.1": - version: 8.35.1 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.35.1" +"@typescript-eslint/tsconfig-utils@npm:8.41.0, @typescript-eslint/tsconfig-utils@npm:^8.41.0": + version: 8.41.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.41.0" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/a11b53e05fbc59eff3f95619847fb7222d8b2aa613e602524c9b700be3ce0d48bcf5e5932869df4658f514bd2cdc87c857d484472af3f3f3adf88b6e7e1c26f3 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/98618a536b9cb071eacba2970ce2ca1b9243de78f4604c2e350823a5275b9d7d15238dbe6acd197c30c0b6cbbf37782c247d14984e1015a109431e4180d76af6 languageName: node linkType: hard @@ -1299,18 +1269,19 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/type-utils@npm:8.34.1" +"@typescript-eslint/type-utils@npm:8.41.0": + version: 8.41.0 + resolution: "@typescript-eslint/type-utils@npm:8.41.0" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.34.1" - "@typescript-eslint/utils": "npm:8.34.1" + "@typescript-eslint/types": "npm:8.41.0" + "@typescript-eslint/typescript-estree": "npm:8.41.0" + "@typescript-eslint/utils": "npm:8.41.0" debug: "npm:^4.3.4" ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/502a2cdfe47f1f34206c747b5a70e0242dd99f570511db3dda9c5f999d9abadfbbb1dfa82a1fa437a1689d232715412e61c97d95f19c9314ba5ad23196b4096d + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/d4f9ae07a30f1cf331c3e3a67f8749b38f199ba5000f7a600492c27f6bec774f15c3553f293c520fb999fb88108665f2785d5261daec1445b17af14a7bb0bfac languageName: node linkType: hard @@ -1321,17 +1292,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.34.1, @typescript-eslint/types@npm:^8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/types@npm:8.34.1" - checksum: 10c0/db1b3dce6a70b28ddb13c76fbb5983240d9395656df5f7cbd99bfd9905e39c0dab2132870f01dbc406b48739c437f7d344a879a824cedaba81b91a53110dc23a - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.35.1, @typescript-eslint/types@npm:^8.35.1": - version: 8.35.1 - resolution: "@typescript-eslint/types@npm:8.35.1" - checksum: 10c0/136dd1c7a39685baa398406423a97a4b6a66e6aed7cbd6ae698a89b0fde92c76f1415294bec612791d67d7917fda280caa65b9d761e2744e8143506d1f417fb2 +"@typescript-eslint/types@npm:8.41.0, @typescript-eslint/types@npm:^8.34.1, @typescript-eslint/types@npm:^8.41.0": + version: 8.41.0 + resolution: "@typescript-eslint/types@npm:8.41.0" + checksum: 10c0/4945a7ed7789e0527833ee378b962416d6d0d61eb6c891fe49cb6c8dc8a9adbfc58676080ca767a1f034f74f9a981caf5f4d4706cba5025c0520a801fb45d7e1 languageName: node linkType: hard @@ -1353,34 +1317,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.34.1" - dependencies: - "@typescript-eslint/project-service": "npm:8.34.1" - "@typescript-eslint/tsconfig-utils": "npm:8.34.1" - "@typescript-eslint/types": "npm:8.34.1" - "@typescript-eslint/visitor-keys": "npm:8.34.1" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.1.0" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/4ee7249db91b9840361f34f80b7b6d646a3af159c7298d79a33d8a11c98792fd3a395343e5e17e0fa29529e8f0113bac8baadcef90d1e140bd736a48f0485042 - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:8.35.1": - version: 8.35.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.35.1" +"@typescript-eslint/typescript-estree@npm:8.41.0": + version: 8.41.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.41.0" dependencies: - "@typescript-eslint/project-service": "npm:8.35.1" - "@typescript-eslint/tsconfig-utils": "npm:8.35.1" - "@typescript-eslint/types": "npm:8.35.1" - "@typescript-eslint/visitor-keys": "npm:8.35.1" + "@typescript-eslint/project-service": "npm:8.41.0" + "@typescript-eslint/tsconfig-utils": "npm:8.41.0" + "@typescript-eslint/types": "npm:8.41.0" + "@typescript-eslint/visitor-keys": "npm:8.41.0" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" @@ -1388,8 +1332,8 @@ __metadata: semver: "npm:^7.6.0" ts-api-utils: "npm:^2.1.0" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/6ef093cf9d7a54a323b3d112c78309b2c24c0f94e2c5b61401db9390eb7ffa3ab1da066c497907d58f0bba6986984ac73a478febd91f0bf11598108cc49f6e02 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/e86233d895403ec4986ced25f56898b2704a84545bb7dfe933f5c64f2ab969dcb7ada7e21ea7e015c875cc94a0767e70573442724960c631b7b3fc556a984c9c languageName: node linkType: hard @@ -1411,33 +1355,18 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/utils@npm:8.34.1" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.34.1" - "@typescript-eslint/types": "npm:8.34.1" - "@typescript-eslint/typescript-estree": "npm:8.34.1" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/e3085877f7940c02a37653e6bc52ac6cde115e755b1f788fe4331202f371b3421cc4d0878c7d3eb054e14e9b3a064496a707a73eac471cb2b73593b9e9d4b998 - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:^8.34.1": - version: 8.35.1 - resolution: "@typescript-eslint/utils@npm:8.35.1" +"@typescript-eslint/utils@npm:8.41.0, @typescript-eslint/utils@npm:^8.34.1": + version: 8.41.0 + resolution: "@typescript-eslint/utils@npm:8.41.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.35.1" - "@typescript-eslint/types": "npm:8.35.1" - "@typescript-eslint/typescript-estree": "npm:8.35.1" + "@typescript-eslint/scope-manager": "npm:8.41.0" + "@typescript-eslint/types": "npm:8.41.0" + "@typescript-eslint/typescript-estree": "npm:8.41.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/1fa4877caae48961d160b88fc974bb7bfe355ca2f8f6915987427354ca23621698041678adab5964caf9ad62c17b349110136890688f13b10ab1aaad74ae63d9 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/3a2ed9b5f801afeccde44dbacdeae0b9c82cc3e1af5e92926929ad86384dc0fb0027152e68c5edfabe904647c2160c0c45ec9c848a8d67c3efb86b78a1343acb languageName: node linkType: hard @@ -1451,23 +1380,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.34.1": - version: 8.34.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.34.1" - dependencies: - "@typescript-eslint/types": "npm:8.34.1" - eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/0e5a9b3d93905d16d3cf8cb5fb346dcc6f760482eb7d0ac209aefc09a32f78ef28a687634df6ad08e81fb3e1083e8805f34472de6bbc501c0105ad654d518f40 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:8.35.1": - version: 8.35.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.35.1" +"@typescript-eslint/visitor-keys@npm:8.41.0": + version: 8.41.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.41.0" dependencies: - "@typescript-eslint/types": "npm:8.35.1" + "@typescript-eslint/types": "npm:8.41.0" eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/55b9eb15842a5d5dca11375e436340c731e01b07190c741d2656330f3e4d88b59e1bf3d677681dd091460be2b6e5f2c42e92faea36f947d25382ead5e8118108 + checksum: 10c0/cfe52e77b9e07c23a4d9f4adf9e6bf27822e58694c9a34fefa4b9fc96d553e9df561971c4da5fc78392522e34696fc1149a76f6a02c328136771c5efe0fd1029 languageName: node linkType: hard @@ -1499,14 +1418,15 @@ __metadata: linkType: hard "@wdio/logger@npm:^9.0.0": - version: 9.15.0 - resolution: "@wdio/logger@npm:9.15.0" + version: 9.18.0 + resolution: "@wdio/logger@npm:9.18.0" dependencies: chalk: "npm:^5.1.2" loglevel: "npm:^1.6.0" loglevel-plugin-prefix: "npm:^0.8.4" + safe-regex2: "npm:^5.0.0" strip-ansi: "npm:^7.1.0" - checksum: 10c0/24f66b97c7a9ac6d9969be18ba0a5d862e31dbfd09b566229927827fd2481a856c1fb7a3a69b675a26706e1d4ae322b7230abb88fb7b3987c901549ed5f1464c + checksum: 10c0/c11cc06ea9f6696a0077f29d0360c10828317e5980815408d04cad99db1a64a6bc1491583ffef681e46e18476f061f37a2e18706f06488715291f3bfc8b32615 languageName: node linkType: hard @@ -1566,9 +1486,9 @@ __metadata: linkType: hard "@xmldom/xmldom@npm:^0.8.8": - version: 0.8.10 - resolution: "@xmldom/xmldom@npm:0.8.10" - checksum: 10c0/c7647c442502720182b0d65b17d45d2d95317c1c8c497626fe524bda79b4fb768a9aa4fae2da919f308e7abcff7d67c058b102a9d641097e9a57f0b80187851f + version: 0.8.11 + resolution: "@xmldom/xmldom@npm:0.8.11" + checksum: 10c0/e768623de72c95d3dae6b5da8e33dda0d81665047811b5498d23a328d45b13feb5536fe921d0308b96a4a8dd8addf80b1f6ef466508051c0b581e63e0dc74ed5 languageName: node linkType: hard @@ -1580,9 +1500,9 @@ __metadata: linkType: hard "@zip.js/zip.js@npm:^2.7.48": - version: 2.7.62 - resolution: "@zip.js/zip.js@npm:2.7.62" - checksum: 10c0/269da31f7514ccbdd8766f0b0c440edb7df53add731862c97f3b27b594182466ce62a03fa4be5ed736a5ac0cd1c5ead09d35c97e27887c8c6f6bfd477102ea8f + version: 2.7.72 + resolution: "@zip.js/zip.js@npm:2.7.72" + checksum: 10c0/26def4367ff242a7203228a74722585f13da4ff1466ace65170f15e77ea956e8d2a9115b917cf8683cb0551c96df716a2489f4e81a557a60f48d28332a480c15 languageName: node linkType: hard @@ -1663,9 +1583,9 @@ __metadata: linkType: hard "agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": - version: 7.1.3 - resolution: "agent-base@npm:7.1.3" - checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe languageName: node linkType: hard @@ -1718,36 +1638,36 @@ __metadata: linkType: hard "allure-commandline@npm:^2.34.0": - version: 2.34.0 - resolution: "allure-commandline@npm:2.34.0" + version: 2.34.1 + resolution: "allure-commandline@npm:2.34.1" bin: allure: bin/allure - checksum: 10c0/73041dd607f2a39ecb458645173b4e74ce12895d8ec4531b925de1f9781bf0b518f5a21f84601aef11d9bd52b5758b988df0a94ea7081f4c73d9b81fccd8e2e0 + checksum: 10c0/8f8d67e0c917d83b2324d760ea8d082c4c8527dcf3feb5b0a1fd4a13cfc995314d447b1084b25b651d68f5fb75d1b1157b0d55bbdc58d23e5e26ea38741df1a4 languageName: node linkType: hard -"allure-js-commons@npm:3.2.2": - version: 3.2.2 - resolution: "allure-js-commons@npm:3.2.2" +"allure-js-commons@npm:3.3.3": + version: 3.3.3 + resolution: "allure-js-commons@npm:3.3.3" dependencies: md5: "npm:^2.3.0" peerDependencies: - allure-playwright: 3.2.2 + allure-playwright: 3.3.3 peerDependenciesMeta: allure-playwright: optional: true - checksum: 10c0/bc56d959ae8bd299566ef63f0430af5ebc7188e3d6525b805c677ff366c5dbd426f2cea37e61d26aceae07579388a3e2ba4450328b633bd99526fc4cc9818f00 + checksum: 10c0/a5b570dba648c23c88b2da4252a3f5e288f135cf12b57cc1ab244233cfeb0dc1ceb75037203b69157935118c37a3f88f2c012049c216fb0fe980ce3f5e3d6c22 languageName: node linkType: hard "allure-playwright@npm:^3.2.2": - version: 3.2.2 - resolution: "allure-playwright@npm:3.2.2" + version: 3.3.3 + resolution: "allure-playwright@npm:3.3.3" dependencies: - allure-js-commons: "npm:3.2.2" + allure-js-commons: "npm:3.3.3" peerDependencies: "@playwright/test": ">=1.36.0" - checksum: 10c0/6a32165487101ab81c57713cbfc64dcf6f7605fd048b113a371ae3f32a1b34ab1e26e76316011db28b2216143598d53b0fa6c283ebc33118dff98a2b0830fa45 + checksum: 10c0/0e7de2c20c92508cebd1b58246205d7fdc2ff53d0ccd537d7814c1affd38fdc6b853eace1e125ba73a74d79528c7a1a242073f41a0b19e9e2defa60afa5fb496 languageName: node linkType: hard @@ -1759,9 +1679,9 @@ __metadata: linkType: hard "ansi-regex@npm:^6.0.1": - version: 6.1.0 - resolution: "ansi-regex@npm:6.1.0" - checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc + version: 6.2.0 + resolution: "ansi-regex@npm:6.2.0" + checksum: 10c0/20a2e55ae9816074a60e6729dbe3daad664cd967fc82acc08b02f5677db84baa688babf940d71f50acbbb184c02459453789705e079f4d521166ae66451de551 languageName: node linkType: hard @@ -1782,8 +1702,8 @@ __metadata: linkType: hard "appium-adb@npm:^12.0.0, appium-adb@npm:^12.12.0, appium-adb@npm:^12.7.0": - version: 12.12.5 - resolution: "appium-adb@npm:12.12.5" + version: 12.13.1 + resolution: "appium-adb@npm:12.13.1" dependencies: "@appium/support": "npm:^6.0.0" async-lock: "npm:^1.0.0" @@ -1795,7 +1715,7 @@ __metadata: semver: "npm:^7.0.0" source-map-support: "npm:^0.x" teen_process: "npm:^2.2.0" - checksum: 10c0/1cf51510e0ba2ff6e58eb441d6cc38c14f04f60eb1cd660bca045e4b9a53eae446a7068feb1c03b6ed57b99fbd375bba1cf4f1f17b8375257b1df3355eb5c1ec + checksum: 10c0/1de6a60e79b2e4959dd7d48ddeb78a54e2967bb228d5b518d6b8fa6558dcd6ba13b61db323d60b6c5f2e1f4ec15cd1bced4c778e704808aa61c5bb57ef2fa5ed languageName: node linkType: hard @@ -1828,8 +1748,8 @@ __metadata: linkType: hard "appium-chromedriver@npm:^7.0.0": - version: 7.0.27 - resolution: "appium-chromedriver@npm:7.0.27" + version: 7.0.35 + resolution: "appium-chromedriver@npm:7.0.35" dependencies: "@appium/base-driver": "npm:^9.1.0" "@appium/support": "npm:^6.0.0" @@ -1844,7 +1764,7 @@ __metadata: source-map-support: "npm:^0.x" teen_process: "npm:^2.2.0" xpath: "npm:^0.x" - checksum: 10c0/5aadfbe716ec656a24417706d9661568cb45702c04cdd1c65cc79e904ec475bfb7fcc14be0d5a40e6d5587fc0a07dd967e841bc01a9ec1491574f0c110e227c6 + checksum: 10c0/83c319571910897fdf3481c31a95215c97827e62ca6c9d3f7a50f3285bd7e4af6841020f5b9e68c9573990ff1f1cfc905a573942fa338cc1628b430065d51d9e languageName: node linkType: hard @@ -1863,8 +1783,8 @@ __metadata: linkType: hard "appium-ios-device@npm:^2.0.0, appium-ios-device@npm:^2.7.25, appium-ios-device@npm:^2.8.0": - version: 2.8.4 - resolution: "appium-ios-device@npm:2.8.4" + version: 2.9.0 + resolution: "appium-ios-device@npm:2.9.0" dependencies: "@appium/support": "npm:^6.0.0" asyncbox: "npm:^3.0.0" @@ -1876,7 +1796,7 @@ __metadata: semver: "npm:^7.0.0" source-map-support: "npm:^0.x" uuid: "npm:^11.0.1" - checksum: 10c0/313cb57d22f672f875ba8786627cc5df685af3972e21cdbbef74a351f76f12a053dfa6043bccb2402f8676ed52fed39795863c60a7950d7f32b7c73d620a2534 + checksum: 10c0/b501d058c96c2c39db6060cb3f277ad2e1333c88f9dd90dac2904eaaa7b655e681169bd46b7530b3e5520148f9fe1e0e536bb11427284f78aae8208a71616386 languageName: node linkType: hard @@ -1900,8 +1820,8 @@ __metadata: linkType: hard "appium-remote-debugger@npm:^12.1.1": - version: 12.2.5 - resolution: "appium-remote-debugger@npm:12.2.5" + version: 12.2.10 + resolution: "appium-remote-debugger@npm:12.2.10" dependencies: "@appium/base-driver": "npm:^9.0.0" "@appium/support": "npm:^6.0.0" @@ -1912,7 +1832,7 @@ __metadata: lodash: "npm:^4.17.11" source-map-support: "npm:^0.x" teen_process: "npm:^2.0.0" - checksum: 10c0/7fb5955a5f2598785908b3b208aaa29e2a82e1a843c46477d913a442e8af29239c9f24c86625e61fe4d8e6d925f7a255e70240d48eda212c69ab1c1d6846dc1a + checksum: 10c0/ef4bab173e131517c4f7b09ea99acf76c156fa1a09dc4ed2d6e62ae045a1b90cdf6d5a4b456cd3bcd2b822c6b45b0248b2d7a5b615f6338f3a09738a4e5df468 languageName: node linkType: hard @@ -1963,9 +1883,9 @@ __metadata: linkType: hard "appium-uiautomator2-server@npm:^7.0.24": - version: 7.6.2 - resolution: "appium-uiautomator2-server@npm:7.6.2" - checksum: 10c0/cd4c46c49cae9e0095d7db318f39cb4ad111322bbe8d91aae3b58d620864844c10ca7e19e2f54c06063ca3e0c4fcc9de873666f8ec4f6f75ecdd0365de9aa521 + version: 7.7.1 + resolution: "appium-uiautomator2-server@npm:7.7.1" + checksum: 10c0/312998f84d1c0550e47f02ccf56e6c7693c4d24d18258f2459ec3e14911de17c7f4051fc4896d4b43bdc23a5dfcf4ff997644ad6997008ce8bf0dc56891eb2b9 languageName: node linkType: hard @@ -2292,13 +2212,13 @@ __metadata: linkType: hard "axios@npm:^1.4.0, axios@npm:^1.6.5, axios@npm:^1.6.7, axios@npm:^1.x": - version: 1.10.0 - resolution: "axios@npm:1.10.0" + version: 1.11.0 + resolution: "axios@npm:1.11.0" dependencies: follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" + form-data: "npm:^4.0.4" proxy-from-env: "npm:^1.1.0" - checksum: 10c0/2239cb269cc789eac22f5d1aabd58e1a83f8f364c92c2caa97b6f5cbb4ab2903d2e557d9dc670b5813e9bcdebfb149e783fb8ab3e45098635cd2f559b06bd5d8 + checksum: 10c0/5de273d33d43058610e4d252f0963cc4f10714da0bfe872e8ef2cbc23c2c999acc300fd357b6bce0fc84a2ca9bd45740fa6bb28199ce2c1266c8b1a393f2b36e languageName: node linkType: hard @@ -2317,15 +2237,15 @@ __metadata: linkType: hard "bare-events@npm:^2.2.0, bare-events@npm:^2.5.4": - version: 2.5.4 - resolution: "bare-events@npm:2.5.4" - checksum: 10c0/877a9cea73d545e2588cdbd6fd01653e27dac48ad6b44985cdbae73e1f57f292d4ba52e25d1fba53674c1053c463d159f3d5c7bc36a2e6e192e389b499ddd627 + version: 2.6.1 + resolution: "bare-events@npm:2.6.1" + checksum: 10c0/948aabf7380120445f7f7b01bd3911c28ad72a8eaa08f6e308bd470b303593d3639309d1a4e5e5c1ab99503a45a18152f474f065be3698bfe68a27ca21f64e37 languageName: node linkType: hard "bare-fs@npm:^4.0.1": - version: 4.1.5 - resolution: "bare-fs@npm:4.1.5" + version: 4.2.1 + resolution: "bare-fs@npm:4.2.1" dependencies: bare-events: "npm:^2.5.4" bare-path: "npm:^3.0.0" @@ -2335,14 +2255,14 @@ __metadata: peerDependenciesMeta: bare-buffer: optional: true - checksum: 10c0/af72ec30bb7844524faa14ae2b74d13b08920b1d839c638da4ad1abdda643958d0b86653d284878a2f9160072b603c9dce55c8cc29da8d84e14ffce1c5d42a01 + checksum: 10c0/16cb6593b69d277bceb03710533682e8677dd8598ebc757cf406faa1f6178446f534726d845519fc77469ad8d86265e8c9f5b419fd93a8c7e30aacc1722ee05d languageName: node linkType: hard "bare-os@npm:^3.0.1": - version: 3.6.1 - resolution: "bare-os@npm:3.6.1" - checksum: 10c0/13064789b3d0d3051d6a89424e6d861c08be101798d69faa78821cffb428b36d1fd4e17c824d5a4939bcd96dbff42c11921494139c8e53c3e520bc0e3f83aeee + version: 3.6.2 + resolution: "bare-os@npm:3.6.2" + checksum: 10c0/7d917bc202b7efbb6b78658403fac04ae4e91db98d38cbd24037f896a2b1b4f4571d8cd408d12bed6a4c406d6abaf8d03836eacbcc4c75a0b6974e268574fc5a languageName: node linkType: hard @@ -2356,8 +2276,8 @@ __metadata: linkType: hard "bare-stream@npm:^2.6.4": - version: 2.6.5 - resolution: "bare-stream@npm:2.6.5" + version: 2.7.0 + resolution: "bare-stream@npm:2.7.0" dependencies: streamx: "npm:^2.21.0" peerDependencies: @@ -2368,7 +2288,7 @@ __metadata: optional: true bare-events: optional: true - checksum: 10c0/1242286f8f3147e9fd353cdaa9cf53226a807ac0dde8177c13f1463aa4cd1f88e07407c883a1b322b901e9af2d1cd30aacd873529031132c384622972e0419df + checksum: 10c0/3acd840b7b288dc066226c36446ff605fba2ecce98f1a0ce6aa611b81aabbcd204046a3209bce172373d17eaeaa5b7d35a85649c18ffcb9f2c783242854e99bd languageName: node linkType: hard @@ -2657,9 +2577,9 @@ __metadata: linkType: hard "chalk@npm:^5.1.2": - version: 5.4.1 - resolution: "chalk@npm:5.4.1" - checksum: 10c0/b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef + version: 5.6.0 + resolution: "chalk@npm:5.6.0" + checksum: 10c0/f8558fc12fd9805f167611803b325b0098bbccdc9f1d3bafead41c9bac61f263357f3c0df0cbe28bc2fd5fca3edcf618b01d6771a5a776b4c15d061482a72b23 languageName: node linkType: hard @@ -3039,9 +2959,9 @@ __metadata: linkType: hard "css-selector-parser@npm:^3.0.0": - version: 3.1.2 - resolution: "css-selector-parser@npm:3.1.2" - checksum: 10c0/4164e68a3684a834feda10665bc85b107c7fd64625c055dca1eab403169bf531cde3425ac4b869633fe536677a90debb5bc85511ff8f464e3acb8f5cb57c3169 + version: 3.1.3 + resolution: "css-selector-parser@npm:3.1.3" + checksum: 10c0/0bba96edfd27827d79933b113c42bec627b96a79f6fe4b12dec12da109d0b3a25f2f76d385b7c28ff22dca68840251751d1061d9226657755430e4787bf4594e languageName: node linkType: hard @@ -3134,9 +3054,9 @@ __metadata: linkType: hard "decamelize@npm:^6.0.0": - version: 6.0.0 - resolution: "decamelize@npm:6.0.0" - checksum: 10c0/689888f5ea39add843d79fb5a8d3bc1ce1df7583899bc7cef081c3deecd54758e24e8692f4c214e0ea6917742bb05ea1991e3e15c33031e7aa7b9041e8e8033a + version: 6.0.1 + resolution: "decamelize@npm:6.0.1" + checksum: 10c0/c0a3a529591ebab1d1a9458b60684194e91d904e9b0a56367d3d507b2c8ab89dfd40c61423ca6a1eb2f70e2d44d2efe78f3342326395d3738d1d42592b1a6224 languageName: node linkType: hard @@ -3277,9 +3197,9 @@ __metadata: linkType: hard "dotenv@npm:^16.4.5": - version: 16.5.0 - resolution: "dotenv@npm:16.5.0" - checksum: 10c0/5bc94c919fbd955bf0ba44d33922a1e93d1078e64a1db5c30faeded1d996e7a83c55332cb8ea4fae5a9ca4d0be44cbceb95c5811e70f9f095298df09d1997dd9 + version: 16.6.1 + resolution: "dotenv@npm:16.6.1" + checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc languageName: node linkType: hard @@ -3571,17 +3491,17 @@ __metadata: linkType: hard "eslint@npm:^9.14.0": - version: 9.29.0 - resolution: "eslint@npm:9.29.0" + version: 9.34.0 + resolution: "eslint@npm:9.34.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.20.1" - "@eslint/config-helpers": "npm:^0.2.1" - "@eslint/core": "npm:^0.14.0" + "@eslint/config-array": "npm:^0.21.0" + "@eslint/config-helpers": "npm:^0.3.1" + "@eslint/core": "npm:^0.15.2" "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.29.0" - "@eslint/plugin-kit": "npm:^0.3.1" + "@eslint/js": "npm:9.34.0" + "@eslint/plugin-kit": "npm:^0.3.5" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" @@ -3616,7 +3536,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/75e3f841e0f8b0fa93dbb2ba6ae538bd8b611c3654117bc3dadf90bb009923dfd2c15ec2948dc6e6b8b571317cc125c5cceb9255da8cd644ee740020df645dd8 + checksum: 10c0/ba3e54fa0c8ed23d062f91519afaae77fed922a6c4d76130b6cd32154bcb406aaea4b3c5ed88e0be40828c1d5b6921592f3947dbdc5e2043de6bd7aa341fe5ea languageName: node linkType: hard @@ -3834,9 +3754,9 @@ __metadata: linkType: hard "fast-uri@npm:^3.0.1": - version: 3.0.6 - resolution: "fast-uri@npm:3.0.6" - checksum: 10c0/74a513c2af0584448aee71ce56005185f81239eab7a2343110e5bad50c39ad4fb19c5a6f99783ead1cac7ccaf3461a6034fda89fffa2b30b6d99b9f21c2f9d29 + version: 3.1.0 + resolution: "fast-uri@npm:3.1.0" + checksum: 10c0/44364adca566f70f40d1e9b772c923138d47efeac2ae9732a872baafd77061f26b097ba2f68f0892885ad177becd065520412b8ffeec34b16c99433c5b9e2de7 languageName: node linkType: hard @@ -3877,14 +3797,14 @@ __metadata: linkType: hard "fdir@npm:^6.4.4": - version: 6.4.6 - resolution: "fdir@npm:6.4.6" + version: 6.5.0 + resolution: "fdir@npm:6.5.0" peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true - checksum: 10c0/45b559cff889934ebb8bc498351e5acba40750ada7e7d6bde197768d2fa67c149be8ae7f8ff34d03f4e1eb20f2764116e56440aaa2f6689e9a4aa7ef06acafe9 + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f languageName: node linkType: hard @@ -4012,12 +3932,12 @@ __metadata: linkType: hard "follow-redirects@npm:^1.15.6": - version: 1.15.9 - resolution: "follow-redirects@npm:1.15.9" + version: 1.15.11 + resolution: "follow-redirects@npm:1.15.11" peerDependenciesMeta: debug: optional: true - checksum: 10c0/5829165bd112c3c0e82be6c15b1a58fa9dcfaede3b3c54697a82fe4a62dd5ae5e8222956b448d2f98e331525f05d00404aba7d696de9e761ef6e42fdc780244f + checksum: 10c0/d301f430542520a54058d4aeeb453233c564aaccac835d29d15e050beb33f339ad67d9bddbce01739c5dc46a6716dbe3d9d0d5134b1ca203effa11a7ef092343 languageName: node linkType: hard @@ -4057,16 +3977,16 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.3 - resolution: "form-data@npm:4.0.3" +"form-data@npm:^4.0.0, form-data@npm:^4.0.4": + version: 4.0.4 + resolution: "form-data@npm:4.0.4" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" es-set-tostringtag: "npm:^2.1.0" hasown: "npm:^2.0.2" mime-types: "npm:^2.1.12" - checksum: 10c0/f0cf45873d600110b5fadf5804478377694f73a1ed97aaa370a74c90cebd7fe6e845a081171668a5476477d0d55a73a4e03d6682968fa8661eac2a81d651fcdb + checksum: 10c0/373525a9a034b9d57073e55eab79e501a714ffac02e7a9b01be1c820780652b16e4101819785e1e18f8d98f0aee866cc654d660a435c378e16a72f2e7cac9695 languageName: node linkType: hard @@ -4112,13 +4032,13 @@ __metadata: linkType: hard "fs-extra@npm:^11.1.1, fs-extra@npm:^11.3.0": - version: 11.3.0 - resolution: "fs-extra@npm:11.3.0" + version: 11.3.1 + resolution: "fs-extra@npm:11.3.1" dependencies: graceful-fs: "npm:^4.2.0" jsonfile: "npm:^6.0.1" universalify: "npm:^2.0.0" - checksum: 10c0/5f95e996186ff45463059feb115a22fb048bdaf7e487ecee8a8646c78ed8fdca63630e3077d4c16ce677051f5e60d3355a06f3cd61f3ca43f48cc58822a44d0a + checksum: 10c0/61e5b7285b1ca72c68dfe1058b2514294a922683afac2a80aa90540f9bd85370763d675e3b408ef500077d355956fece3bd24b546790e261c3d3015967e2b2d9 languageName: node linkType: hard @@ -4310,13 +4230,13 @@ __metadata: linkType: hard "get-uri@npm:^6.0.1": - version: 6.0.4 - resolution: "get-uri@npm:6.0.4" + version: 6.0.5 + resolution: "get-uri@npm:6.0.5" dependencies: basic-ftp: "npm:^5.0.2" data-uri-to-buffer: "npm:^6.0.2" debug: "npm:^4.3.4" - checksum: 10c0/07c87abe1f97a4545fae329a37a45e276ec57e6ad48dad2a97780f87c96b00a82c2043ab49e1a991f99bb5cff8f8ed975e44e4f8b3c9600f35493a97f123499f + checksum: 10c0/c7ff5d5d55de53d23ecce7c5108cc3ed0db1174db43c9aa15506d640283d36ee0956fd8ba1fc50b06a718466cc85794ae9d8860193f91318afe846e3e7010f3a languageName: node linkType: hard @@ -4768,8 +4688,8 @@ __metadata: linkType: hard "io.appium.settings@npm:^5.12.11, io.appium.settings@npm:^5.12.22": - version: 5.14.12 - resolution: "io.appium.settings@npm:5.14.12" + version: 5.14.15 + resolution: "io.appium.settings@npm:5.14.15" dependencies: "@appium/logger": "npm:^1.3.0" asyncbox: "npm:^3.0.0" @@ -4778,17 +4698,14 @@ __metadata: semver: "npm:^7.5.4" source-map-support: "npm:^0.x" teen_process: "npm:^2.0.0" - checksum: 10c0/2d393a5c34ab9edeb33b96c86edd9151cd7fbc73b3b816e1535549017555781b10a663231a76f5a450bf31aad32d9d21683df88cc0b459898a89a25a187208f2 + checksum: 10c0/428afdf404d7297475646fee5afa217dc5fb3e9b347080eefded372d54517321aaa0137435ab54666d0e42a0232f00187113b08ca828670022b6b1eb190b7668 languageName: node linkType: hard -"ip-address@npm:^9.0.5": - version: 9.0.5 - resolution: "ip-address@npm:9.0.5" - dependencies: - jsbn: "npm:1.1.0" - sprintf-js: "npm:^1.1.3" - checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc +"ip-address@npm:^10.0.1": + version: 10.0.1 + resolution: "ip-address@npm:10.0.1" + checksum: 10c0/1634d79dae18394004775cb6d699dc46b7c23df6d2083164025a2b15240c1164fccde53d0e08bd5ee4fc53913d033ab6b5e395a809ad4b956a940c446e948843 languageName: node linkType: hard @@ -5001,13 +4918,6 @@ __metadata: languageName: node linkType: hard -"jsbn@npm:1.1.0": - version: 1.1.0 - resolution: "jsbn@npm:1.1.0" - checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 - languageName: node - linkType: hard - "jsbn@npm:~0.1.0": version: 0.1.1 resolution: "jsbn@npm:0.1.1" @@ -5100,15 +5010,15 @@ __metadata: linkType: hard "jsonfile@npm:^6.0.1": - version: 6.1.0 - resolution: "jsonfile@npm:6.1.0" + version: 6.2.0 + resolution: "jsonfile@npm:6.2.0" dependencies: graceful-fs: "npm:^4.1.6" universalify: "npm:^2.0.0" dependenciesMeta: graceful-fs: optional: true - checksum: 10c0/4f95b5e8a5622b1e9e8f33c96b7ef3158122f595998114d1e7f03985649ea99cb3cd99ce1ed1831ae94c8c8543ab45ebd044207612f31a56fd08462140e46865 + checksum: 10c0/7f4f43b08d1869ded8a6822213d13ae3b99d651151d77efd1557ced0889c466296a7d9684e397bd126acf5eb2cfcb605808c3e681d0fdccd2fe5a04b47e76c0d languageName: node linkType: hard @@ -5304,7 +5214,7 @@ __metadata: languageName: node linkType: hard -"lodash.get@npm:^4, lodash.get@npm:^4.4.2": +"lodash.get@npm:^4": version: 4.4.2 resolution: "lodash.get@npm:4.4.2" checksum: 10c0/48f40d471a1654397ed41685495acb31498d5ed696185ac8973daef424a749ca0c7871bf7b665d5c14f5cc479394479e0307e781f61d5573831769593411be6e @@ -6010,8 +5920,8 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 11.2.0 - resolution: "node-gyp@npm:11.2.0" + version: 11.4.2 + resolution: "node-gyp@npm:11.4.2" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" @@ -6025,7 +5935,7 @@ __metadata: which: "npm:^5.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10c0/bd8d8c76b06be761239b0c8680f655f6a6e90b48e44d43415b11c16f7e8c15be346fba0cbf71588c7cdfb52c419d928a7d3db353afc1d952d19756237d8f10b9 + checksum: 10c0/0bfd3e96770ed70f07798d881dd37b4267708966d868a0e585986baac487d9cf5831285579fd629a83dc4e434f53e6416ce301097f2ee464cb74d377e4d8bdbe languageName: node linkType: hard @@ -6522,9 +6432,9 @@ __metadata: linkType: hard "picomatch@npm:^4.0.2": - version: 4.0.2 - resolution: "picomatch@npm:4.0.2" - checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 languageName: node linkType: hard @@ -6546,27 +6456,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.53.1": - version: 1.53.1 - resolution: "playwright-core@npm:1.53.1" +"playwright-core@npm:1.55.0": + version: 1.55.0 + resolution: "playwright-core@npm:1.55.0" bin: playwright-core: cli.js - checksum: 10c0/8780552740741b94c346373ab6f949e9a44d3df54016b12123da9fe2f5fb60c1838197642916789b0a2ffb567e4ba0089f2e57d496bef7e460ddc4526f9b3b0f + checksum: 10c0/c39d6aa30e7a4e73965942ca5e13405ae05c9cb49f755a35f04248c864c0b24cf662d9767f1797b3ec48d1cf4e54774dce4a19c16534bd5cfd2aa3da81c9dc3a languageName: node linkType: hard -"playwright@npm:1.53.1": - version: 1.53.1 - resolution: "playwright@npm:1.53.1" +"playwright@npm:1.55.0": + version: 1.55.0 + resolution: "playwright@npm:1.55.0" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.53.1" + playwright-core: "npm:1.55.0" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10c0/3dcbcd3791a7f0d1007db4d3126c8136258a2002c2fbfc8b840086b64cbc1fb628b2e25dd3b345d685fb27ba39790ab0f4ce81a8faade7dc748af3a63bdf3550 + checksum: 10c0/51605b7e57a5650e57972c5fdfc09d7a9934cca1cbee5beacca716fa801e25cb5bb7c1663de90c22b300fde884e5545a2b13a0505a93270b660687791c478304 languageName: node linkType: hard @@ -6613,11 +6523,11 @@ __metadata: linkType: hard "prettier@npm:^3.3.3": - version: 3.5.3 - resolution: "prettier@npm:3.5.3" + version: 3.6.2 + resolution: "prettier@npm:3.6.2" bin: prettier: bin/prettier.cjs - checksum: 10c0/3880cb90b9dc0635819ab52ff571518c35bd7f15a6e80a2054c05dbc8a3aa6e74f135519e91197de63705bcb38388ded7e7230e2178432a1468005406238b877 + checksum: 10c0/488cb2f2b99ec13da1e50074912870217c11edaddedeadc649b1244c749d15ba94e846423d062e2c4c9ae683e2d65f754de28889ba06e697ac4f988d44f45812 languageName: node linkType: hard @@ -6998,6 +6908,13 @@ __metadata: languageName: node linkType: hard +"ret@npm:~0.5.0": + version: 0.5.0 + resolution: "ret@npm:0.5.0" + checksum: 10c0/220868b194f87bf1998e32e409086eec6b39e860c052bf267f8ad4d0131706a9773d45fd3f91acfb1a7c928fce002b694ab86fdba90bc8d4b8df68fa8645c5cc + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -7100,6 +7017,15 @@ __metadata: languageName: node linkType: hard +"safe-regex2@npm:^5.0.0": + version: 5.0.0 + resolution: "safe-regex2@npm:5.0.0" + dependencies: + ret: "npm:~0.5.0" + checksum: 10c0/83d5b1b60a5a97cb71a6e615518ec4a47761b3600aba389089be59a417498185250db2368080afc2f5e91237d68809c6c634b97a2e1cc8bd56a4c7eef2eeb6cf + languageName: node + linkType: hard + "safe-stable-stringify@npm:^2.3.1": version: 2.5.0 resolution: "safe-stable-stringify@npm:2.5.0" @@ -7281,30 +7207,31 @@ __metadata: linkType: hard "sharp@npm:^0.34.1": - version: 0.34.2 - resolution: "sharp@npm:0.34.2" - dependencies: - "@img/sharp-darwin-arm64": "npm:0.34.2" - "@img/sharp-darwin-x64": "npm:0.34.2" - "@img/sharp-libvips-darwin-arm64": "npm:1.1.0" - "@img/sharp-libvips-darwin-x64": "npm:1.1.0" - "@img/sharp-libvips-linux-arm": "npm:1.1.0" - "@img/sharp-libvips-linux-arm64": "npm:1.1.0" - "@img/sharp-libvips-linux-ppc64": "npm:1.1.0" - "@img/sharp-libvips-linux-s390x": "npm:1.1.0" - "@img/sharp-libvips-linux-x64": "npm:1.1.0" - "@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0" - "@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0" - "@img/sharp-linux-arm": "npm:0.34.2" - "@img/sharp-linux-arm64": "npm:0.34.2" - "@img/sharp-linux-s390x": "npm:0.34.2" - "@img/sharp-linux-x64": "npm:0.34.2" - "@img/sharp-linuxmusl-arm64": "npm:0.34.2" - "@img/sharp-linuxmusl-x64": "npm:0.34.2" - "@img/sharp-wasm32": "npm:0.34.2" - "@img/sharp-win32-arm64": "npm:0.34.2" - "@img/sharp-win32-ia32": "npm:0.34.2" - "@img/sharp-win32-x64": "npm:0.34.2" + version: 0.34.3 + resolution: "sharp@npm:0.34.3" + dependencies: + "@img/sharp-darwin-arm64": "npm:0.34.3" + "@img/sharp-darwin-x64": "npm:0.34.3" + "@img/sharp-libvips-darwin-arm64": "npm:1.2.0" + "@img/sharp-libvips-darwin-x64": "npm:1.2.0" + "@img/sharp-libvips-linux-arm": "npm:1.2.0" + "@img/sharp-libvips-linux-arm64": "npm:1.2.0" + "@img/sharp-libvips-linux-ppc64": "npm:1.2.0" + "@img/sharp-libvips-linux-s390x": "npm:1.2.0" + "@img/sharp-libvips-linux-x64": "npm:1.2.0" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0" + "@img/sharp-linux-arm": "npm:0.34.3" + "@img/sharp-linux-arm64": "npm:0.34.3" + "@img/sharp-linux-ppc64": "npm:0.34.3" + "@img/sharp-linux-s390x": "npm:0.34.3" + "@img/sharp-linux-x64": "npm:0.34.3" + "@img/sharp-linuxmusl-arm64": "npm:0.34.3" + "@img/sharp-linuxmusl-x64": "npm:0.34.3" + "@img/sharp-wasm32": "npm:0.34.3" + "@img/sharp-win32-arm64": "npm:0.34.3" + "@img/sharp-win32-ia32": "npm:0.34.3" + "@img/sharp-win32-x64": "npm:0.34.3" color: "npm:^4.2.3" detect-libc: "npm:^2.0.4" semver: "npm:^7.7.2" @@ -7335,6 +7262,8 @@ __metadata: optional: true "@img/sharp-linux-arm64": optional: true + "@img/sharp-linux-ppc64": + optional: true "@img/sharp-linux-s390x": optional: true "@img/sharp-linux-x64": @@ -7351,7 +7280,7 @@ __metadata: optional: true "@img/sharp-win32-x64": optional: true - checksum: 10c0/43967dbaaf1e1140a2f43b51d54762cc1bba01648392e355028568e4838833bf1abc2a96c09b893e6407b0c59a2c271d66e8d56a582aa6c951d476ab83a37fba + checksum: 10c0/df9e6645e3db6ed298a0ac956ba74e468c367fc038b547936fbdddc6a29fce9af40413acbef73b3716291530760f311a20e45c8983f20ee5ea69dd2f21464a2b languageName: node linkType: hard @@ -7503,12 +7432,12 @@ __metadata: linkType: hard "socks@npm:^2.8.3": - version: 2.8.5 - resolution: "socks@npm:2.8.5" + version: 2.8.7 + resolution: "socks@npm:2.8.7" dependencies: - ip-address: "npm:^9.0.5" + ip-address: "npm:^10.0.1" smart-buffer: "npm:^4.2.0" - checksum: 10c0/e427d0eb0451cfd04e20b9156ea8c0e9b5e38a8d70f21e55c30fbe4214eda37cfc25d782c63f9adc5fbdad6d062a0f127ef2cefc9a44b6fee2b9ea5d1ed10827 + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 languageName: node linkType: hard @@ -7564,9 +7493,9 @@ __metadata: linkType: hard "spdx-license-ids@npm:^3.0.0": - version: 3.0.21 - resolution: "spdx-license-ids@npm:3.0.21" - checksum: 10c0/ecb24c698d8496aa9efe23e0b1f751f8a7a89faedcdfcbfabae772b546c2db46ccde8f3bc447a238eb86bbcd4f73fea88720ef3b8394f7896381bec3d7736411 + version: 3.0.22 + resolution: "spdx-license-ids@npm:3.0.22" + checksum: 10c0/4a85e44c2ccfc06eebe63239193f526508ebec1abc7cf7bca8ee43923755636234395447c2c87f40fb672cf580a9c8e684513a676bfb2da3d38a4983684bbb38 languageName: node linkType: hard @@ -7604,13 +7533,6 @@ __metadata: languageName: node linkType: hard -"sprintf-js@npm:^1.1.3": - version: 1.1.3 - resolution: "sprintf-js@npm:1.1.3" - checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec - languageName: node - linkType: hard - "sshpk@npm:^1.7.0": version: 1.18.0 resolution: "sshpk@npm:1.18.0" @@ -7829,8 +7751,8 @@ __metadata: linkType: hard "tar-fs@npm:^3.0.6": - version: 3.0.10 - resolution: "tar-fs@npm:3.0.10" + version: 3.1.0 + resolution: "tar-fs@npm:3.1.0" dependencies: bare-fs: "npm:^4.0.1" bare-path: "npm:^3.0.0" @@ -7841,7 +7763,7 @@ __metadata: optional: true bare-path: optional: true - checksum: 10c0/b8e8c1c3e9be6928a6252b491a58fe435ad01e3d7c62959cf7796f3f733d07a5ccece8b1ab81ea94c18167388759f33b6d59d6022103cdbb40a4c7c7879b63ab + checksum: 10c0/760309677543c03fbc253b5ef1ab4bb2ceafb554471b6cbe4930d1633f35662ec26a1414c66fa6754f5aa7e8c65003f73849242f624c322d3dcba7a8888a6915 languageName: node linkType: hard @@ -8190,20 +8112,21 @@ __metadata: linkType: hard "typescript-eslint@npm:^8.15.0": - version: 8.34.1 - resolution: "typescript-eslint@npm:8.34.1" + version: 8.41.0 + resolution: "typescript-eslint@npm:8.41.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.34.1" - "@typescript-eslint/parser": "npm:8.34.1" - "@typescript-eslint/utils": "npm:8.34.1" + "@typescript-eslint/eslint-plugin": "npm:8.41.0" + "@typescript-eslint/parser": "npm:8.41.0" + "@typescript-eslint/typescript-estree": "npm:8.41.0" + "@typescript-eslint/utils": "npm:8.41.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/6de5d2ce180d1609a8a5383557a2787f17620ebc9a4d84fba9d9240db8005cc3084a7840ebafe532fba9970fe12822ee415615041f3527334fdfc45c411d35b6 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/e141ffaf0332053483526a31e68755fe1438f6367571f39e67e32c0e15d509e8678adab2020597720b0307360493724d8dcf60d0620611d7e1e209d7f952cfe9 languageName: node linkType: hard -"typescript@npm:5.8.3, typescript@npm:^5.6.3": +"typescript@npm:5.8.3": version: 5.8.3 resolution: "typescript@npm:5.8.3" bin: @@ -8213,7 +8136,17 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.8.3#optional!builtin, typescript@patch:typescript@npm%3A^5.6.3#optional!builtin": +"typescript@npm:^5.6.3": + version: 5.9.2 + resolution: "typescript@npm:5.9.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/cd635d50f02d6cf98ed42de2f76289701c1ec587a363369255f01ed15aaf22be0813226bff3c53e99d971f9b540e0b3cc7583dbe05faded49b1b0bed2f638a18 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A5.8.3#optional!builtin": version: 5.8.3 resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5adc0c" bin: @@ -8223,6 +8156,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.6.3#optional!builtin": + version: 5.9.2 + resolution: "typescript@patch:typescript@npm%3A5.9.2#optional!builtin::version=5.9.2&hash=5adc0c" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/66fc07779427a7c3fa97da0cf2e62595eaff2cea4594d45497d294bfa7cb514d164f0b6ce7a5121652cf44c0822af74e29ee579c771c405e002d1f23cf06bfde + languageName: node + linkType: hard + "unbzip2-stream@npm:1.4.3": version: 1.4.3 resolution: "unbzip2-stream@npm:1.4.3" @@ -8240,10 +8183,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~7.8.0": - version: 7.8.0 - resolution: "undici-types@npm:7.8.0" - checksum: 10c0/9d9d246d1dc32f318d46116efe3cfca5a72d4f16828febc1918d94e58f6ffcf39c158aa28bf5b4fc52f410446bc7858f35151367bd7a49f21746cab6497b709b +"undici-types@npm:~7.10.0": + version: 7.10.0 + resolution: "undici-types@npm:7.10.0" + checksum: 10c0/8b00ce50e235fe3cc601307f148b5e8fb427092ee3b23e8118ec0a5d7f68eca8cee468c8fc9f15cbb2cf2a3797945ebceb1cbd9732306a1d00e0a9b6afa0f635 languageName: node linkType: hard @@ -8681,7 +8624,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.18.2, ws@npm:^8.0.0, ws@npm:^8.13.0, ws@npm:^8.8.0": +"ws@npm:8.18.2": version: 8.18.2 resolution: "ws@npm:8.18.2" peerDependencies: @@ -8696,6 +8639,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.0.0, ws@npm:^8.13.0, ws@npm:^8.8.0": + version: 8.18.3 + resolution: "ws@npm:8.18.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + languageName: node + linkType: hard + "xmlbuilder@npm:^15.1.1": version: 15.1.1 resolution: "xmlbuilder@npm:15.1.1" From 9a056b6adc55e5a6c0bd7966d60336a1d1b77473 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Thu, 11 Sep 2025 13:44:00 +1000 Subject: [PATCH 06/30] fix: ensure lfs screenshots are always pulled chore: debug risk level try to debug lfs issues fix: ensure screenshots in lfs are always downloaded chore: remove debugging artifacts --- .github/workflows/android-regression.yml | 34 ++++++++++++++++++++++ run/test/specs/utils/verify_screenshots.ts | 11 +++++++ 2 files changed, 45 insertions(+) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index d57010a34..f9a55268b 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -110,6 +110,40 @@ jobs: with: lfs: true + # This is necessary because the macOS runner was not always pulling the lfs files despite checkout with lfs: true + # Check if LFS files were actually pulled + - name: Verify LFS files + run: | + echo "Ensuring all LFS screenshot files are downloaded..." + git lfs pull --include="run/screenshots/**/*.png" + + echo "=== Verifying screenshot files ===" + corrupted_files=0 + total_files=0 + + for file in run/screenshots/**/*.png; do + if [[ -f "$file" ]]; then # Make sure file exists (in case glob doesn't match anything) + ((total_files++)) + if file "$file" | grep -q "PNG image"; then + echo "✅ $file" + else + echo "⚠️ WARNING: $file is not a PNG image - related tests may fail" + # Show what it actually is for debugging + echo " File type: $(file "$file")" + ((corrupted_files++)) + fi + fi + done + + echo "📊 Screenshot verification: $((total_files - corrupted_files))/$total_files files valid" + + if [ $corrupted_files -gt 0 ]; then + echo "⚠️ Found $corrupted_files corrupted screenshot files" + echo "📝 Tests using these files will be skipped or may fail" + else + echo "✅ All screenshot files are valid PNG images" + fi + - name: Fetch result history from gh-pages uses: ./github/actions/fetch-allure-history if: ${{ env.ALLURE_ENABLED == 'true' }} diff --git a/run/test/specs/utils/verify_screenshots.ts b/run/test/specs/utils/verify_screenshots.ts index 70c458622..5c8cf6ff2 100644 --- a/run/test/specs/utils/verify_screenshots.ts +++ b/run/test/specs/utils/verify_screenshots.ts @@ -60,10 +60,13 @@ export async function verifyElementScreenshot< const elementScreenshotBase64: string = await device.getElementScreenshot( elementToScreenshot.ELEMENT ); + // Convert the base64 string to a Buffer and save it to disk as a png const elementScreenshotPath = path.join(diffsDir, `${uuid}_screenshot.png`); const screenshotBuffer = Buffer.from(elementScreenshotBase64, 'base64'); + fs.writeFileSync(elementScreenshotPath, screenshotBuffer); + // Check if baseline screenshot exists const baselineScreenshotPath = element.screenshotFileName(...args); if (!fs.existsSync(baselineScreenshotPath)) { @@ -71,10 +74,18 @@ export async function verifyElementScreenshot< `No baseline image found at: ${baselineScreenshotPath}. A new screenshot has been saved at: ${elementScreenshotPath}` ); } + + // Fail loudly if LFS pointer has not been resolved correctly + const baselineBuffer = fs.readFileSync(baselineScreenshotPath); + if (baselineBuffer.toString('utf8', 0, 50).includes('version https://git-lfs')) { + throw new Error(`Baseline is corrupted LFS pointer: ${baselineScreenshotPath}. Skipping visual test.`); +} + // Use looks-same to verify the element screenshot against the baseline const { equal, diffImage } = await looksSame(elementScreenshotPath, baselineScreenshotPath, { createDiffImage: true, }); + if (!equal) { const diffImagePath = path.join(diffsDir, `${uuid}_diffImage.png`); await diffImage.save(diffImagePath); From e201f45f91cf0320af3fc6f3a4924b7b3dbeb1cd Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Thu, 11 Sep 2025 17:21:34 +1000 Subject: [PATCH 07/30] wip: tidy up PR --- .github/workflows/android-regression.yml | 6 +- run/test/specs/utils/capabilities_android.ts | 4 - scripts/ci.sh | 80 +++++++++----------- 3 files changed, 36 insertions(+), 54 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index f9a55268b..89378d55c 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -162,11 +162,7 @@ jobs: - name: Download APK & extract it run: | pwd - if [[ "${{ runner.os }}" == "macOS" ]]; then - curl -L -o session-android.apk.tar.xz ${{ github.event.inputs.APK_URL }} - else - wget -q -O session-android.apk.tar.xz ${{ github.event.inputs.APK_URL }} - fi + curl -L -o session-android.apk.tar.xz ${{ github.event.inputs.APK_URL }} tar xf session-android.apk.tar.xz mv session-android-*universal extracted diff --git a/run/test/specs/utils/capabilities_android.ts b/run/test/specs/utils/capabilities_android.ts index c0ca7fd06..3bb752fb8 100644 --- a/run/test/specs/utils/capabilities_android.ts +++ b/run/test/specs/utils/capabilities_android.ts @@ -35,7 +35,6 @@ const emulator5Udid = 'emulator-5562'; const emulator6Udid = 'emulator-5564'; const emulator7Udid = 'emulator-5566'; const emulator8Udid = 'emulator-5568'; -const emulator9Udid = 'emulator-5570'; const udids = [ emulator1Udid, @@ -46,7 +45,6 @@ const udids = [ emulator6Udid, emulator7Udid, emulator8Udid, - emulator9Udid, ]; const emulatorCapabilities: AppiumCapabilities[] = udids.map(udid => ({ @@ -63,7 +61,6 @@ const emulatorCapabilities5 = emulatorCapabilities[4]; const emulatorCapabilities6 = emulatorCapabilities[5]; const emulatorCapabilities7 = emulatorCapabilities[6]; const emulatorCapabilities8 = emulatorCapabilities[7]; -const emulatorCapabilities9 = emulatorCapabilities[8]; export const androidCapabilities = { sharedCapabilities, @@ -80,7 +77,6 @@ function getAllCaps() { emulatorCapabilities6, emulatorCapabilities7, emulatorCapabilities8, - emulatorCapabilities9, ]; return emulatorCaps; } diff --git a/scripts/ci.sh b/scripts/ci.sh index 4b293cf02..0b50160bf 100644 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -2,70 +2,71 @@ set -x # Android functions -# Detect platform and set variables accordingly +# Common configuration +TARGET="google_apis_playstore" +EMULATOR_DEVICE="pixel_6" +EMULATOR_BIN="emulator" + +# Platform-specific configuration if [[ "$OSTYPE" == "darwin"* ]]; then - # Mac settings + # Mac ARM64 settings ARCH="arm64-v8a" EMULATOR_COUNT=6 API_LEVEL="35" ANDROID_CMD="commandlinetools-mac-13114758_latest.zip" - EMULATOR_BIN="emulator" - TARGET="google_apis_playstore" # Mac ARM doesn't have playstore variant ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT:-"$HOME/Android/sdk"} + RAM_SIZE="2048" + EMULATOR_PROCESS="qemu-system-aarch64-headless" else - # Linux settings + # Linux x86_64 settings ARCH="x86_64" EMULATOR_COUNT=4 API_LEVEL="34" ANDROID_CMD="commandlinetools-linux-13114758_latest.zip" - EMULATOR_BIN="emulator" - TARGET="google_apis_playstore" # Linux can use playstore ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT:-"/opt/android"} + RAM_SIZE="4192" + EMULATOR_PROCESS="qemu-system-x86_64" fi - -# Derive build tools version from API level +# Derived configuration (uses the variables set above) BUILD_TOOLS="${API_LEVEL}.0.0" -ANDROID_ARCH=${ANDROID_ARCH_DEFAULT} ANDROID_API_LEVEL="android-${API_LEVEL}" -ANDROID_APIS="${TARGET};${ARCH}" -EMULATOR_PACKAGE="system-images;${ANDROID_API_LEVEL};${ANDROID_APIS}" +EMULATOR_PACKAGE="system-images;${ANDROID_API_LEVEL};${TARGET};${ARCH}" PLATFORM_VERSION="platforms;${ANDROID_API_LEVEL}" BUILD_TOOL="build-tools;${BUILD_TOOLS}" -export ANDROID_SDK_PACKAGES="${EMULATOR_PACKAGE} ${PLATFORM_VERSION} ${BUILD_TOOL} platform-tools" -export ANDROID_SDK_ROOT +ANDROID_SDK_PACKAGES="${EMULATOR_PACKAGE} ${PLATFORM_VERSION} ${BUILD_TOOL} platform-tools emulator" -export PATH="$PATH:$ANDROID_SDK_ROOT/cmdline-tools/tools:$ANDROID_SDK_ROOT/cmdline-tools/tools/bin:$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/tools/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/build-tools/${BUILD_TOOLS}:$ANDROID_SDK_ROOT/platform-tools/" -export EMULATOR_DEVICE="pixel_6" # all emulators are created with the pixel 6 spec for now +# Export everything +export ARCH EMULATOR_BIN EMULATOR_COUNT EMULATOR_PROCESS API_LEVEL ANDROID_CMD TARGET ANDROID_SDK_ROOT RAM_SIZE +export BUILD_TOOLS ANDROID_API_LEVEL EMULATOR_PACKAGE PLATFORM_VERSION BUILD_TOOL ANDROID_SDK_PACKAGES EMULATOR_DEVICE +export PATH="$ANDROID_SDK_ROOT/cmdline-tools/tools/bin:$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/build-tools/${BUILD_TOOLS}:$PATH" # this should only be done when we bump the API version or add a worker to the CI that needs emulators to be setup # Once you've run this, you must also start_for_snapshots() and force_save_snapshots() (see details below) function create_emulators() { - # Skip apt install on Mac + # Skip apt install on Mac if [[ "$OSTYPE" != "darwin"* ]]; then sudo apt update - sudo apt install -y ca-certificates curl git vim bash wget unzip tree htop gzip default-jre libnss3 libxcursor1 libqt5gui5 libc++-dev libxcb-cursor0 htop tree tar gzip gh nload + sudo apt install -y ca-certificates curl git vim bash wget unzip tree htop gzip default-jre libnss3 libxcursor1 libqt5gui5 libc++-dev libxcb-cursor0 tar gh nload fi sudo rm -rf $ANDROID_SDK_ROOT - sudo mkdir -p $ANDROID_SDK_ROOT + if [[ "$OSTYPE" == "darwin"* ]]; then sudo chown $USER:staff $ANDROID_SDK_ROOT else sudo chown $USER:$USER $ANDROID_SDK_ROOT fi - # Download the SDK tools - if [[ "$OSTYPE" == "darwin"* ]]; then - curl -L -o /tmp/${ANDROID_CMD} https://dl.google.com/android/repository/${ANDROID_CMD} - else - wget https://dl.google.com/android/repository/${ANDROID_CMD} -P /tmp - fi - # Check if download succeeded - if [[ ! -f /tmp/${ANDROID_CMD} ]]; then - echo "Failed to download Android SDK tools" + # Download SDK tools + local download_url="https://dl.google.com/android/repository/${ANDROID_CMD}" + echo "Downloading Android SDK tools..." + curl -fL -o "/tmp/${ANDROID_CMD}" "$download_url" + + if [[ ! -f "/tmp/${ANDROID_CMD}" ]]; then + echo "Error: Failed to download Android SDK tools" >&2 return 1 fi @@ -93,14 +94,13 @@ function create_emulators() { for i in $(seq 1 $EMULATOR_COUNT) do echo "no" | avdmanager --verbose create avd --force --name "emulator$i" --device "${EMULATOR_DEVICE}" --package "${EMULATOR_PACKAGE}" - CONFIG_FILE="$HOME/.android/avd/emulator$i.avd/config.ini" + # Configure RAM for each AVD + local config_file="$HOME/.android/avd/emulator$i.avd/config.ini" if [[ "$OSTYPE" == "darwin"* ]]; then - # Mac: 9 emulators @ 2GB each - sed -i '' 's/^hw\.ramSize=.*/hw.ramSize=2048/' "$CONFIG_FILE" + sed -i '' "s/^hw\.ramSize=.*/hw.ramSize=${RAM_SIZE}/" "$config_file" else - # Linux: Keep original 4GB - sed -i 's/^hw\.ramSize=.*/hw.ramSize=4192/' "$CONFIG_FILE" + sed -i "s/^hw\.ramSize=.*/hw.ramSize=${RAM_SIZE}/" "$config_file" fi done } @@ -108,13 +108,7 @@ function create_emulators() { function start_for_snapshots() { for i in $(seq 1 $EMULATOR_COUNT) do - if [[ "$OSTYPE" == "darwin"* ]]; then - # Mac: Not headless - $EMULATOR_BIN @emulator$i -no-snapshot-load & - else - # Linux: Keep as-is with display - DISPLAY=:0 $EMULATOR_BIN @emulator$i -gpu host -accel on -no-snapshot-load & - fi + $EMULATOR_BIN @emulator$i -gpu host -accel on -no-snapshot-load & sleep 20 done } @@ -131,11 +125,7 @@ function force_save_snapshots() { } function killall_emulators() { - if [[ "$OSTYPE" == "darwin"* ]]; then - killall qemu-system-aarch64-headless 2>/dev/null || true - else - killall qemu-system-x86_64 2>/dev/null || true - fi + killall "$EMULATOR_PROCESS" 2>/dev/null || true } From 27d25bd6841c44b8a11743df582b7080edfdfa85 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Fri, 12 Sep 2025 16:27:54 +1000 Subject: [PATCH 08/30] feat: allow ios tests to run 4 workers --- .github/workflows/ios-regression.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ios-regression.yml b/.github/workflows/ios-regression.yml index 85dc2559d..fe2dfb4d8 100644 --- a/.github/workflows/ios-regression.yml +++ b/.github/workflows/ios-regression.yml @@ -67,7 +67,8 @@ on: - '1' - '2' - '3' - default: '3' + - '4' + default: '4' HIDE_WEBDRIVER_LOGS: description: 'print webdriver logs (1 to hide, 0 to show). PRINT_ONGOING_TEST_LOGS or PRINT_FAILED_TEST_LOGS must be 1' required: true @@ -94,7 +95,7 @@ jobs: PLAYWRIGHT_RETRIES_COUNT: ${{ github.event.inputs.PLAYWRIGHT_RETRIES_COUNT }} PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.PRINT_FAILED_TEST_LOGS }} PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.PRINT_ONGOING_TEST_LOGS }} - PLAYWRIGHT_WORKERS_COUNT: 3 # for iOS, this is the max we can have on our self-hosted runner + PLAYWRIGHT_WORKERS_COUNT: ${{ github.event.inputs.PLAYWRIGHT_WORKERS_COUNT }} SDK_MANAGER_FULL_PATH: '' AVD_MANAGER_FULL_PATH: '' ANDROID_SYSTEM_IMAGE: '' @@ -157,7 +158,7 @@ jobs: - name: Run the iOS tests​​ (all device counts) env: - DEVICES_PER_TEST_COUNT: 4 + DEVICES_PER_TEST_COUNT: 3 run: | pwd _TESTING=${{ github.event.inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test --grep "(?=.*@${PLATFORM})(?=.*@${{ github.event.inputs.RISK }})" #Note: this has to be double quotes From cfe9f23b6a5f81b746397d32315d9346c3eec5af Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 10:31:49 +1000 Subject: [PATCH 09/30] fix: simplify android workflow with composite action --- .github/workflows/android-regression.yml | 75 ++++++++++-------------- github/actions/run-tests/action.yml | 69 ++++++++++++++++++++++ 2 files changed, 101 insertions(+), 43 deletions(-) create mode 100644 github/actions/run-tests/action.yml diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 89378d55c..7d00c4da0 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -81,7 +81,10 @@ jobs: runs-on: ${{ github.event.inputs.RUNNER == 'mac' && fromJSON('["self-hosted", "macOS"]') || fromJSON('["self-hosted", "linux", "X64", "qa-android"]') }} env: PLATFORM: 'android' - EMULATOR_COUNT: ${{ github.event.inputs.RUNNER == 'mac' && '6' || '4' }} + EMULATOR_COUNT: ${{ github.event.inputs.RUNNER == 'mac' && '6' || '4' }} # macOS runner can support 6 emulators, linux runner only 4 + WORKERS_1_DEVICE: ${{ github.event.inputs.RUNNER == 'mac' && '6' || '4' }} # 6 or 4 workers, 1 device each + WORKERS_2_DEVICE: ${{ github.event.inputs.RUNNER == 'mac' && '3' || '2' }} # 3 or 2 workers, 2 devices each + WORKERS_3_DEVICE: ${{ github.event.inputs.RUNNER == 'mac' && '2' || '1' }} # 2 or 1 worker(s), 3 devices each APK_URL: ${{ github.event.inputs.APK_URL }} BUILD_NUMBER: ${{ github.event.inputs.BUILD_NUMBER }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -202,47 +205,42 @@ jobs: PLATFORM: ${{ env.PLATFORM }} RISK: ${{ github.event.inputs.RISK }} - - name: Run the 1-devices tests ​​with 4 workers - continue-on-error: true - id: devices-1-test-run - env: - PLAYWRIGHT_WORKERS_COUNT: ${{ env.EMULATOR_COUNT }} # 6 on mac, 4 on linux - DEVICES_PER_TEST_COUNT: 1 - run: | - pwd - TESTING=${{ github.event.inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test --grep "(?=.*@${PLATFORM})(?=.*@${DEVICES_PER_TEST_COUNT}-devices)(?=.*@${{ github.event.inputs.RISK }})" #Note: this has to be double quotes - - - name: Upload results of this run - uses: ./github/actions/upload-test-results + - name: Run 1-device tests + uses: ./github/actions/run-playwright-tests with: PLATFORM: ${{ env.PLATFORM }} + RISK: ${{ github.event.inputs.RISK }} + WORKERS_COUNT: ${{ env.WORKERS_1_DEVICE }} + DEVICES_PER_TEST: '1' + TEST_NAME: '1-device' + GREP_INCLUDE: '@1-devices' + HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} UPLOAD_IDENTIFIER: 'devices-1-test-run' - - name: Run the 2-devices tests ​​with 2 workers - continue-on-error: true - id: devices-2-test-run - env: - PLAYWRIGHT_WORKERS_COUNT: ${{ github.event.inputs.RUNNER == 'mac' && '3' || '2' }} - DEVICES_PER_TEST_COUNT: 2 - run: | - pwd - _TESTING=${{ github.event.inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test --grep "(?=.*@${PLATFORM})(?=.*@${DEVICES_PER_TEST_COUNT}-devices)(?=.*@${{ github.event.inputs.RISK }})" #Note: this has to be double quotes - - - name: Upload results of this run - uses: ./github/actions/upload-test-results + - name: Run 2-device tests + uses: ./github/actions/run-playwright-tests with: PLATFORM: ${{ env.PLATFORM }} + RISK: ${{ github.event.inputs.RISK }} + WORKERS_COUNT: ${{ env.WORKERS_2_DEVICE }} + DEVICES_PER_TEST: '2' + TEST_NAME: '2-device' + GREP_INCLUDE: '@2-devices' + HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} UPLOAD_IDENTIFIER: 'devices-2-test-run' - - name: Run the tests ​​other tests with 1 worker - continue-on-error: true - id: other-devices-test-run - env: - PLAYWRIGHT_WORKERS_COUNT: ${{ github.event.inputs.RUNNER == 'mac' && '2' || '1' }} - DEVICES_PER_TEST_COUNT: 3 - run: | - pwd - _TESTING=${{ github.event.inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test --grep "(?=.*@${PLATFORM})(?=.*@${{ github.event.inputs.RISK }})" --grep-invert "@1-devices|@2-devices" #Note: this has to be double quotes + - name: Run 3-device tests + uses: ./github/actions/run-playwright-tests + with: + PLATFORM: ${{ env.PLATFORM }} + RISK: ${{ github.event.inputs.RISK }} + WORKERS_COUNT: ${{ env.WORKERS_3_DEVICE }} + DEVICES_PER_TEST: '3' + TEST_NAME: '3-device' + GREP_INCLUDE: '@3-devices' + HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} + UPLOAD_IDENTIFIER: 'devices-3-test-run' + - name: Generate and publish test report uses: ./github/actions/generate-publish-test-report @@ -256,21 +254,12 @@ jobs: GITHUB_RUN_NUMBER: ${{ github.run_number }} GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }} - - name: Upload results of this run - uses: ./github/actions/upload-test-results - with: - PLATFORM: ${{ env.PLATFORM }} - UPLOAD_IDENTIFIER: 'devices-other-test-run' - - name: Upload csv of this whole run uses: ./github/actions/upload-csv-test-results if: always() with: PLATFORM: ${{ env.PLATFORM }} - - name: Check if any tests failed - if: steps.devices-1-test-run != 'success' || steps.devices-2-test-run != 'success' || steps.other-devices-test-run.outcome != 'success' - run: echo 'Some test failed, see above'; exit 1 - name: Stop emulators if: always() diff --git a/github/actions/run-tests/action.yml b/github/actions/run-tests/action.yml new file mode 100644 index 000000000..05708e6f2 --- /dev/null +++ b/github/actions/run-tests/action.yml @@ -0,0 +1,69 @@ +name: 'Run Android Tests' +description: 'Run Android tests with specified configuration' + +inputs: + PLATFORM: + description: 'Platform to test (android/ios)' + required: true + RISK: + description: 'Risk level to test' + required: true + WORKERS_COUNT: + description: 'Number of Playwright workers' + required: true + DEVICES_PER_TEST: + description: 'Number of devices per test' + required: true + TEST_NAME: + description: 'Name for this test run (for logging)' + required: true + GREP_INCLUDE: + description: 'Pattern to include in grep' + required: false + default: '' + HIDE_WEBDRIVER_LOGS: + description: 'Hide webdriver logs (1 to hide, 0 to show)' + required: true + UPLOAD_IDENTIFIER: + description: 'Unique identifier for uploading results' + required: true + +runs: + using: 'composite' + steps: + - name: Run ${{ inputs.TEST_NAME }} tests + id: run-tests + continue-on-error: true + shell: bash + env: + PLAYWRIGHT_WORKERS_COUNT: ${{ inputs.WORKERS_COUNT }} + DEVICES_PER_TEST_COUNT: ${{ inputs.DEVICES_PER_TEST }} + run: | + echo "🚀 Running ${{ inputs.TEST_NAME }} tests" + echo " Workers: ${{ inputs.WORKERS_COUNT }}" + echo " Devices per test: ${{ inputs.DEVICES_PER_TEST }}" + echo " Total emulator usage: $((${{ inputs.WORKERS_COUNT }} * ${{ inputs.DEVICES_PER_TEST }}))" + + # Build the base grep pattern + base_pattern="(?=.*@${{ inputs.PLATFORM }})" + + # Add risk filter if specified + if [[ "${{ inputs.RISK }}" != "" ]]; then + base_pattern="${base_pattern}(?=.*@${{ inputs.RISK }})" + fi + + # Add include pattern if specified + if [[ "${{ inputs.GREP_INCLUDE }}" != "" ]]; then + base_pattern="${base_pattern}(?=.*${{ inputs.GREP_INCLUDE }})" + fi + + # Run the tests + _TESTING=${{ inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test \ + --grep "$base_pattern" + fi + + - name: Upload ${{ inputs.TEST_NAME }} results + uses: ./github/actions/upload-test-results + with: + PLATFORM: ${{ inputs.PLATFORM }} + UPLOAD_IDENTIFIER: ${{ inputs.UPLOAD_IDENTIFIER }} \ No newline at end of file From c2c6ae9f27bb79c80cc4e99265e9fa82ebaae24a Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 10:44:29 +1000 Subject: [PATCH 10/30] fix: correct composite action name --- .github/workflows/android-regression.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 7d00c4da0..54a5c1eec 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -206,7 +206,7 @@ jobs: RISK: ${{ github.event.inputs.RISK }} - name: Run 1-device tests - uses: ./github/actions/run-playwright-tests + uses: ./github/actions/run-tests with: PLATFORM: ${{ env.PLATFORM }} RISK: ${{ github.event.inputs.RISK }} @@ -218,7 +218,7 @@ jobs: UPLOAD_IDENTIFIER: 'devices-1-test-run' - name: Run 2-device tests - uses: ./github/actions/run-playwright-tests + uses: ./github/actions/run-tests with: PLATFORM: ${{ env.PLATFORM }} RISK: ${{ github.event.inputs.RISK }} @@ -230,7 +230,7 @@ jobs: UPLOAD_IDENTIFIER: 'devices-2-test-run' - name: Run 3-device tests - uses: ./github/actions/run-playwright-tests + uses: ./github/actions/run-tests with: PLATFORM: ${{ env.PLATFORM }} RISK: ${{ github.event.inputs.RISK }} From 32e43aab8504a85ae067f7e2bdb8129755e7e749 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 10:49:19 +1000 Subject: [PATCH 11/30] feat: simplify logging --- .github/workflows/android-regression.yml | 40 ++++++++---------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 54a5c1eec..195d6b472 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -51,30 +51,15 @@ on: - '2' default: '0' - PRINT_FAILED_TEST_LOGS: - description: 'print failed test logs (1 to enable) - DONT DO FOR FULL REGRESSION (it crashes github)' + LOG_LEVEL: + description: 'Test logging verbosity (WARNING: anything other than minimal mode may crash GitHub Actions on large test runs)' required: true type: choice options: - - '0' - - '1' - default: '0' - PRINT_ONGOING_TEST_LOGS: - description: 'print ongoing test logs (1 to enable) - DONT DO FOR FULL REGRESSION (it crashes github)' - required: true - type: choice - options: - - '0' - - '1' - default: '0' - HIDE_WEBDRIVER_LOGS: - description: 'print webdriver logs (1 to hide, 0 to show). PRINT_ONGOING_TEST_LOGS or PRINT_FAILED_TEST_LOGS must be 1' - required: true - type: choice - options: - - '0' - - '1' - default: '1' + - 'minimal' # Recommended for full regressions + - 'failures' # Show failed test logs only + - 'verbose' # All test logs - use with caution! + default: 'minimal' jobs: android-regression: @@ -93,8 +78,9 @@ jobs: IOS_APP_PATH_PREFIX: '' ANDROID_APK: './extracted/session-android.apk' PLAYWRIGHT_RETRIES_COUNT: ${{ github.event.inputs.PLAYWRIGHT_RETRIES_COUNT }} - PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.PRINT_FAILED_TEST_LOGS }} - PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.PRINT_ONGOING_TEST_LOGS }} + PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'failures' || github.event.inputs.LOG_LEVEL == 'verbose' ? '1' : '0' }} + PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'verbose' ? '1' : '0' }} + HIDE_WEBDRIVER_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'minimal' ? '1' : '0' }} IOS_1_SIMULATOR: '' IOS_2_SIMULATOR: '' IOS_3_SIMULATOR: '' @@ -151,15 +137,15 @@ jobs: uses: ./github/actions/fetch-allure-history if: ${{ env.ALLURE_ENABLED == 'true' }} with: - PLATFORM: ${{env.PLATFORM}} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN}} + PLATFORM: ${{ env.PLATFORM }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: ./github/actions/print-runner-details with: APK_URL: ${{ github.event.inputs.APK_URL }} RISK: ${{ github.event.inputs.RISK }} - PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.PRINT_FAILED_TEST_LOGS }} - PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.PRINT_ONGOING_TEST_LOGS }} + PRINT_FAILED_TEST_LOGS: ${{ env.PRINT_FAILED_TEST_LOGS }} + PRINT_ONGOING_TEST_LOGS: ${{ env.PRINT_ONGOING_TEST_LOGS }} PLAYWRIGHT_RETRIES_COUNT: ${{ github.event.inputs.PLAYWRIGHT_RETRIES_COUNT }} - name: Download APK & extract it From 597cc00b05ca8c6c216a2dcf5f3128310f2328d0 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 10:52:05 +1000 Subject: [PATCH 12/30] chore: linting --- .github/workflows/android-regression.yml | 6 ++-- .github/workflows/ios-regression.yml | 2 +- github/actions/run-tests/action.yml | 10 +++--- .../group_disappearing_messages_image.spec.ts | 32 +++++++++---------- .../specs/linked_device_create_group.spec.ts | 4 +-- .../specs/linked_device_restore_group.spec.ts | 4 +-- run/test/specs/locators/index.ts | 2 +- run/test/specs/utils/binaries.ts | 14 ++++---- run/test/specs/utils/verify_screenshots.ts | 8 +++-- run/types/DeviceWrapper.ts | 12 +++---- 10 files changed, 45 insertions(+), 49 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 195d6b472..3cd13e9c9 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -56,9 +56,9 @@ on: required: true type: choice options: - - 'minimal' # Recommended for full regressions - - 'failures' # Show failed test logs only - - 'verbose' # All test logs - use with caution! + - 'minimal' + - 'failures' + - 'verbose' default: 'minimal' jobs: diff --git a/.github/workflows/ios-regression.yml b/.github/workflows/ios-regression.yml index fe2dfb4d8..ae8b921e9 100644 --- a/.github/workflows/ios-regression.yml +++ b/.github/workflows/ios-regression.yml @@ -95,7 +95,7 @@ jobs: PLAYWRIGHT_RETRIES_COUNT: ${{ github.event.inputs.PLAYWRIGHT_RETRIES_COUNT }} PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.PRINT_FAILED_TEST_LOGS }} PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.PRINT_ONGOING_TEST_LOGS }} - PLAYWRIGHT_WORKERS_COUNT: ${{ github.event.inputs.PLAYWRIGHT_WORKERS_COUNT }} + PLAYWRIGHT_WORKERS_COUNT: ${{ github.event.inputs.PLAYWRIGHT_WORKERS_COUNT }} SDK_MANAGER_FULL_PATH: '' AVD_MANAGER_FULL_PATH: '' ANDROID_SYSTEM_IMAGE: '' diff --git a/github/actions/run-tests/action.yml b/github/actions/run-tests/action.yml index 05708e6f2..a36593a77 100644 --- a/github/actions/run-tests/action.yml +++ b/github/actions/run-tests/action.yml @@ -43,20 +43,20 @@ runs: echo " Workers: ${{ inputs.WORKERS_COUNT }}" echo " Devices per test: ${{ inputs.DEVICES_PER_TEST }}" echo " Total emulator usage: $((${{ inputs.WORKERS_COUNT }} * ${{ inputs.DEVICES_PER_TEST }}))" - + # Build the base grep pattern base_pattern="(?=.*@${{ inputs.PLATFORM }})" - + # Add risk filter if specified if [[ "${{ inputs.RISK }}" != "" ]]; then base_pattern="${base_pattern}(?=.*@${{ inputs.RISK }})" fi - + # Add include pattern if specified if [[ "${{ inputs.GREP_INCLUDE }}" != "" ]]; then base_pattern="${base_pattern}(?=.*${{ inputs.GREP_INCLUDE }})" fi - + # Run the tests _TESTING=${{ inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test \ --grep "$base_pattern" @@ -66,4 +66,4 @@ runs: uses: ./github/actions/upload-test-results with: PLATFORM: ${{ inputs.PLATFORM }} - UPLOAD_IDENTIFIER: ${{ inputs.UPLOAD_IDENTIFIER }} \ No newline at end of file + UPLOAD_IDENTIFIER: ${{ inputs.UPLOAD_IDENTIFIER }} diff --git a/run/test/specs/group_disappearing_messages_image.spec.ts b/run/test/specs/group_disappearing_messages_image.spec.ts index d0ec55817..2081defa8 100644 --- a/run/test/specs/group_disappearing_messages_image.spec.ts +++ b/run/test/specs/group_disappearing_messages_image.spec.ts @@ -47,23 +47,23 @@ async function disappearingImageMessageGroup(platform: SupportedPlatformsType, t ) ); } -if (platform === 'android') { - await Promise.all([ - alice1.hasElementBeenDeleted({ - ...new MediaMessage(alice1).build(), - maxWait, - preventEarlyDeletion: true, - }), - // Bob and Charlie haven't trusted the message - ...[bob1, charlie1].map(device => - device.hasElementBeenDeleted({ - strategy: 'accessibility id', - selector: 'Untrusted attachment message', + if (platform === 'android') { + await Promise.all([ + alice1.hasElementBeenDeleted({ + ...new MediaMessage(alice1).build(), maxWait, preventEarlyDeletion: true, - }) - ) - ]); -} + }), + // Bob and Charlie haven't trusted the message + ...[bob1, charlie1].map(device => + device.hasElementBeenDeleted({ + strategy: 'accessibility id', + selector: 'Untrusted attachment message', + maxWait, + preventEarlyDeletion: true, + }) + ), + ]); + } await closeApp(alice1, bob1, charlie1); } diff --git a/run/test/specs/linked_device_create_group.spec.ts b/run/test/specs/linked_device_create_group.spec.ts index 25acd1cb7..759433c05 100644 --- a/run/test/specs/linked_device_create_group.spec.ts +++ b/run/test/specs/linked_device_create_group.spec.ts @@ -55,9 +55,7 @@ async function linkedGroup(platform: SupportedPlatformsType, testInfo: TestInfo) // Control message should be "Group name is now {group_name}." await alice1.waitForControlMessageToBePresent(groupNameNew); // Check linked device for name change (conversation header name) - await alice2.waitForTextElementToBePresent( - new ConversationHeaderName(alice2, newGroupName) - ); + await alice2.waitForTextElementToBePresent(new ConversationHeaderName(alice2, newGroupName)); await Promise.all( [alice1, alice2, bob1].map(device => device.waitForTextElementToBePresent(new ConversationHeaderName(device, newGroupName)) diff --git a/run/test/specs/linked_device_restore_group.spec.ts b/run/test/specs/linked_device_restore_group.spec.ts index cff2f315b..70f1ddea6 100644 --- a/run/test/specs/linked_device_restore_group.spec.ts +++ b/run/test/specs/linked_device_restore_group.spec.ts @@ -39,9 +39,7 @@ async function restoreGroup(platform: SupportedPlatformsType, testInfo: TestInfo // Check that group has loaded on linked device await unknown1.clickOnElementAll(new ConversationItem(unknown1, testGroupName)); // Check the group name has loaded - await unknown1.waitForTextElementToBePresent( - new ConversationHeaderName(unknown1, testGroupName) - ); + await unknown1.waitForTextElementToBePresent(new ConversationHeaderName(unknown1, testGroupName)); // Check all messages are present await Promise.all([ unknown1.waitForTextElementToBePresent(new MessageBody(unknown1, aliceMessage)), diff --git a/run/test/specs/locators/index.ts b/run/test/specs/locators/index.ts index 2939f355a..2e449da58 100644 --- a/run/test/specs/locators/index.ts +++ b/run/test/specs/locators/index.ts @@ -388,7 +388,7 @@ export class BlockedContactsSettings extends LocatorsInterface { case 'ios': return { strategy: 'accessibility id', - selector: 'Block contacts - Navigation' + selector: 'Block contacts - Navigation', }; } } diff --git a/run/test/specs/utils/binaries.ts b/run/test/specs/utils/binaries.ts index 1011df107..9460305f3 100644 --- a/run/test/specs/utils/binaries.ts +++ b/run/test/specs/utils/binaries.ts @@ -1,4 +1,4 @@ -import { existsSync, lstatSync} from 'fs'; +import { existsSync, lstatSync } from 'fs'; import { toNumber } from 'lodash'; import * as path from 'path'; @@ -45,15 +45,15 @@ export const getAvdManagerFullPath = () => { const possiblePaths = [ path.join(sdkRoot, 'cmdline-tools', 'latest', 'bin', 'avdmanager'), path.join(sdkRoot, 'cmdline-tools', 'tools', 'bin', 'avdmanager'), - path.join(sdkRoot, 'tools', 'bin', 'avdmanager') + path.join(sdkRoot, 'tools', 'bin', 'avdmanager'), ]; - + for (const path of possiblePaths) { if (existsSync(path) && lstatSync(path).isFile()) { return path; } } - + throw new Error(`avdmanager not found in any expected location under ${sdkRoot}`); }; @@ -63,15 +63,15 @@ export const getSdkManagerFullPath = () => { const possiblePaths = [ path.join(sdkRoot, 'cmdline-tools', 'latest', 'bin', 'sdkmanager'), path.join(sdkRoot, 'cmdline-tools', 'tools', 'bin', 'sdkmanager'), - path.join(sdkRoot, 'tools', 'bin', 'sdkmanager') + path.join(sdkRoot, 'tools', 'bin', 'sdkmanager'), ]; - + for (const path of possiblePaths) { if (existsSync(path) && lstatSync(path).isFile()) { return path; } } - + throw new Error(`sdkmanager not found in any expected location under ${sdkRoot}`); }; diff --git a/run/test/specs/utils/verify_screenshots.ts b/run/test/specs/utils/verify_screenshots.ts index 5c8cf6ff2..1727ff662 100644 --- a/run/test/specs/utils/verify_screenshots.ts +++ b/run/test/specs/utils/verify_screenshots.ts @@ -75,11 +75,13 @@ export async function verifyElementScreenshot< ); } - // Fail loudly if LFS pointer has not been resolved correctly + // Fail loudly if LFS pointer has not been resolved correctly const baselineBuffer = fs.readFileSync(baselineScreenshotPath); if (baselineBuffer.toString('utf8', 0, 50).includes('version https://git-lfs')) { - throw new Error(`Baseline is corrupted LFS pointer: ${baselineScreenshotPath}. Skipping visual test.`); -} + throw new Error( + `Baseline is corrupted LFS pointer: ${baselineScreenshotPath}. Skipping visual test.` + ); + } // Use looks-same to verify the element screenshot against the baseline const { equal, diffImage } = await looksSame(elementScreenshotPath, baselineScreenshotPath, { diff --git a/run/types/DeviceWrapper.ts b/run/types/DeviceWrapper.ts index c6663f5a5..4238a6365 100644 --- a/run/types/DeviceWrapper.ts +++ b/run/types/DeviceWrapper.ts @@ -329,9 +329,7 @@ export class DeviceWrapper { { strategy: 'id' as Strategy, pattern: /resource-id="([^"]+)"/g }, ]; - const blacklist = [ - { from: 'Voice message', to: 'New voice message'}, - ] + const blacklist = [{ from: 'Voice message', to: 'New voice message' }]; // System locators such as 'network.loki.messenger.qa:id' can cause false positives with too high similarity scores // Strip any known prefix patterns first @@ -378,14 +376,14 @@ export class DeviceWrapper { const selectorConfidence = ((1 - result.score) * 100).toFixed(2); const isBlacklisted = blacklist.some( - pair => - (selector.includes(pair.from) && match.originalSelector.includes(pair.to) || - selector.includes(pair.to) && match.originalSelector.includes(pair.from)) + pair => + (selector.includes(pair.from) && match.originalSelector.includes(pair.to)) || + (selector.includes(pair.to) && match.originalSelector.includes(pair.from)) ); // Don't heal blacklisted pairs if (isBlacklisted) { - continue; + continue; } // Sometimes the element is just not on screen yet - skip From 11ca0fb31da6c0c9e005bef4c037b02572d147a2 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 10:55:07 +1000 Subject: [PATCH 13/30] fix: simplify logging --- .github/workflows/android-regression.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 3cd13e9c9..b3c4d44e3 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -78,9 +78,9 @@ jobs: IOS_APP_PATH_PREFIX: '' ANDROID_APK: './extracted/session-android.apk' PLAYWRIGHT_RETRIES_COUNT: ${{ github.event.inputs.PLAYWRIGHT_RETRIES_COUNT }} - PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'failures' || github.event.inputs.LOG_LEVEL == 'verbose' ? '1' : '0' }} - PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'verbose' ? '1' : '0' }} - HIDE_WEBDRIVER_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'minimal' ? '1' : '0' }} + PRINT_FAILED_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL != 'minimal' && '1' || '0' }} + PRINT_ONGOING_TEST_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'verbose' && '1' || '0' }} + HIDE_WEBDRIVER_LOGS: ${{ github.event.inputs.LOG_LEVEL == 'minimal' && '1' || '0' }} IOS_1_SIMULATOR: '' IOS_2_SIMULATOR: '' IOS_3_SIMULATOR: '' From d10d94da5015fa5604e365b79e4da4519e3e3afe Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 10:59:50 +1000 Subject: [PATCH 14/30] feat: mac emulators to start from cold boot --- .github/workflows/android-regression.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index b3c4d44e3..afeb1163c 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -178,13 +178,16 @@ jobs: adb kill-server; adb start-server; - - name: Start emulators from snapshot + - name: Start emulators shell: bash - run: | + run: | # macOS can handle cold boot, linux cannot source ./scripts/ci.sh - start_for_snapshots + if [[ "${{ github.event.inputs.RUNNER }}" == "mac" ]]; then + start_for_snapshots + else + start_with_snapshots + fi wait_for_emulators - - name: List tests part of this run uses: ./github/actions/list-tests with: From bca639b881efd0220ec325e0a4bd450d92e404dc Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 11:07:21 +1000 Subject: [PATCH 15/30] fix: no default value needed --- run/test/specs/state_builder/index.ts | 67 +-------------------------- 1 file changed, 1 insertion(+), 66 deletions(-) diff --git a/run/test/specs/state_builder/index.ts b/run/test/specs/state_builder/index.ts index 9748900b0..133f7a2db 100644 --- a/run/test/specs/state_builder/index.ts +++ b/run/test/specs/state_builder/index.ts @@ -206,7 +206,7 @@ export async function open_Alice1_Bob1_Charlie1_friends_group({ export async function open_Alice1_Bob1_friends_group_Unknown1({ platform, groupName, - focusGroupConvo = true, + focusGroupConvo, testInfo, }: WithPlatform & WithFocusGroupConvo & { @@ -258,71 +258,6 @@ export async function open_Alice1_Bob1_friends_group_Unknown1({ }; } -/** - * Open 4 devices, one for Alice, one for Bob, one for Charlie, and one extra, unlinked. - * This function is used for testing that we can do a bunch of actions without having a linked device, - * and then that linking a new device recovers the correct state. - */ -export async function open_Alice1_Bob1_Charlie1_Unknown1({ - platform, - groupName, - focusGroupConvo = true, - testInfo, -}: WithPlatform & - WithFocusGroupConvo & { - groupName: string; - testInfo: TestInfo; - }) { - const stateToBuildKey = '3friendsInGroup'; - const appsToOpen = 4; - const result = await openAppsWithState({ - platform, - appsToOpen, - stateToBuildKey, - groupName, - testInfo, - }); - result.devices[0].setDeviceIdentity('alice1'); - result.devices[1].setDeviceIdentity('bob1'); - result.devices[2].setDeviceIdentity('charlie1'); - result.devices[3].setDeviceIdentity('unknown1'); // this device will be linked later - const seedPhrases = result.prebuilt.users.map(m => m.seedPhrase); - await linkDevices(result.devices.slice(0, -1), seedPhrases); - - const formattedGroup = { group: result.prebuilt.group }; - - const alice1 = result.devices[0]; - const bob1 = result.devices[1]; - const charlie1 = result.devices[2]; - - const formattedDevices = { - alice1, - bob1, - charlie1, - unknown1: result.devices[3], // not assigned yet - }; - const alice = result.prebuilt.users[0]; - const bob = result.prebuilt.users[1]; - const charlie = result.prebuilt.users[2]; - const formattedUsers: WithUsers<3> = { - alice, - bob, - charlie, - }; - if (focusGroupConvo) { - await focusConvoOnDevices({ - // slice off the last device as it will be used later (i.e. we don't want to link yet) - devices: result.devices.slice(0, -1), - convoName: result.prebuilt.group.groupName, - }); - } - - return { - devices: formattedDevices, - prebuilt: { ...formattedUsers, ...formattedGroup }, - }; -} - export async function open_Alice2({ platform, testInfo }: WithPlatform & { testInfo: TestInfo }) { const prebuiltStateKey = '1user'; const appsToOpen = 2; From 063cae78a2b29f99b48299caa760ff85b1602ca3 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 11:10:49 +1000 Subject: [PATCH 16/30] chore: cleanup --- .../specs/linked_device_create_group.spec.ts | 55 +------------------ 1 file changed, 1 insertion(+), 54 deletions(-) diff --git a/run/test/specs/linked_device_create_group.spec.ts b/run/test/specs/linked_device_create_group.spec.ts index 759433c05..6177126a3 100644 --- a/run/test/specs/linked_device_create_group.spec.ts +++ b/run/test/specs/linked_device_create_group.spec.ts @@ -65,57 +65,4 @@ async function linkedGroup(platform: SupportedPlatformsType, testInfo: TestInfo) [alice2, bob1].map(device => device.waitForControlMessageToBePresent(groupNameNew)) ); await closeApp(alice1, alice2, bob1); -} - -// async function linkedGroupAndroid(platform: SupportedPlatformsType, testInfo: TestInfo) { -// const testGroupName = 'Test group'; -// const newGroupName = 'Changed group name'; -// const { device1, device2, device3, device4 } = await openAppFourDevices(platform, testInfo); -// // Create users A, B and C -// const alice = await linkedDevice(device1, device2, USERNAME.ALICE); -// const [bob, charlie] = await Promise.all([ -// newUser(device3, USERNAME.BOB), -// newUser(device4, USERNAME.CHARLIE), -// ]); -// // Create group -// // Note we keep this createGroup here as we want it to **indeed** use the UI to create the group -// await createGroup(platform, device1, alice, device3, bob, device4, charlie, testGroupName); -// // Test that group has loaded on linked device -// await device2.clickOnElementAll(new ConversationItem(device2, testGroupName)); -// // Click on settings or three dots -// await device1.clickOnElementAll(new ConversationSettings(device1)); -// // Click on Edit group option -// await sleepFor(1000); -// await device1.clickOnElementAll(new UpdateGroupInformation(device1)); -// // Click on current group name -// await device1.clickOnElementAll(new EditGroupNameInput(device1)); -// // Remove current group name -// await device1.deleteText(new EditGroupNameInput(device1)); -// // Enter new group name (same test tag for both) -// await device1.clickOnElementAll(new EditGroupNameInput(device1)); -// await device1.inputText(newGroupName, new EditGroupNameInput(device1)); -// // Click done/apply -// await device1.clickOnElementAll(new SaveGroupNameChangeButton(device1)); -// await device1.navigateBack(); -// // Check control message for changed name -// const groupNameNew = englishStrippedStr('groupNameNew') -// .withArgs({ group_name: newGroupName }) -// .toString(); -// // Config message is "Group name is now {group_name}" -// await device1.waitForControlMessageToBePresent(groupNameNew); -// // Check linked device for name change (conversation header name) -// await device2.waitForTextElementToBePresent( -// new ConversationHeaderName(device2).build(newGroupName) -// ); -// await Promise.all([ -// device2.waitForControlMessageToBePresent(groupNameNew), -// device3.waitForControlMessageToBePresent(groupNameNew), -// device4.waitForControlMessageToBePresent(groupNameNew), -// ]); -// await closeApp(device1, device2, device3, device4); -// } - -// // TODO -// // Remove user -// // Add user -// // Disappearing messages +} \ No newline at end of file From 35ce679fbc26c04120a2cc76ef41262f1298889f Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 11:19:45 +1000 Subject: [PATCH 17/30] fix: simplify workflow files --- .github/workflows/android-regression.yml | 10 ------ github/actions/run-tests/action.yml | 44 +++++------------------- 2 files changed, 9 insertions(+), 45 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index afeb1163c..42035fa3b 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -201,10 +201,7 @@ jobs: RISK: ${{ github.event.inputs.RISK }} WORKERS_COUNT: ${{ env.WORKERS_1_DEVICE }} DEVICES_PER_TEST: '1' - TEST_NAME: '1-device' - GREP_INCLUDE: '@1-devices' HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} - UPLOAD_IDENTIFIER: 'devices-1-test-run' - name: Run 2-device tests uses: ./github/actions/run-tests @@ -213,10 +210,7 @@ jobs: RISK: ${{ github.event.inputs.RISK }} WORKERS_COUNT: ${{ env.WORKERS_2_DEVICE }} DEVICES_PER_TEST: '2' - TEST_NAME: '2-device' - GREP_INCLUDE: '@2-devices' HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} - UPLOAD_IDENTIFIER: 'devices-2-test-run' - name: Run 3-device tests uses: ./github/actions/run-tests @@ -225,11 +219,7 @@ jobs: RISK: ${{ github.event.inputs.RISK }} WORKERS_COUNT: ${{ env.WORKERS_3_DEVICE }} DEVICES_PER_TEST: '3' - TEST_NAME: '3-device' - GREP_INCLUDE: '@3-devices' HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} - UPLOAD_IDENTIFIER: 'devices-3-test-run' - - name: Generate and publish test report uses: ./github/actions/generate-publish-test-report diff --git a/github/actions/run-tests/action.yml b/github/actions/run-tests/action.yml index a36593a77..0d4325a6f 100644 --- a/github/actions/run-tests/action.yml +++ b/github/actions/run-tests/action.yml @@ -14,24 +14,14 @@ inputs: DEVICES_PER_TEST: description: 'Number of devices per test' required: true - TEST_NAME: - description: 'Name for this test run (for logging)' - required: true - GREP_INCLUDE: - description: 'Pattern to include in grep' - required: false - default: '' HIDE_WEBDRIVER_LOGS: description: 'Hide webdriver logs (1 to hide, 0 to show)' required: true - UPLOAD_IDENTIFIER: - description: 'Unique identifier for uploading results' - required: true runs: using: 'composite' steps: - - name: Run ${{ inputs.TEST_NAME }} tests + - name: Run ${{ inputs.DEVICES_PER_TEST }}-device tests id: run-tests continue-on-error: true shell: bash @@ -39,31 +29,15 @@ runs: PLAYWRIGHT_WORKERS_COUNT: ${{ inputs.WORKERS_COUNT }} DEVICES_PER_TEST_COUNT: ${{ inputs.DEVICES_PER_TEST }} run: | - echo "🚀 Running ${{ inputs.TEST_NAME }} tests" - echo " Workers: ${{ inputs.WORKERS_COUNT }}" - echo " Devices per test: ${{ inputs.DEVICES_PER_TEST }}" - echo " Total emulator usage: $((${{ inputs.WORKERS_COUNT }} * ${{ inputs.DEVICES_PER_TEST }}))" - - # Build the base grep pattern - base_pattern="(?=.*@${{ inputs.PLATFORM }})" - - # Add risk filter if specified - if [[ "${{ inputs.RISK }}" != "" ]]; then - base_pattern="${base_pattern}(?=.*@${{ inputs.RISK }})" - fi - - # Add include pattern if specified - if [[ "${{ inputs.GREP_INCLUDE }}" != "" ]]; then - base_pattern="${base_pattern}(?=.*${{ inputs.GREP_INCLUDE }})" - fi - - # Run the tests - _TESTING=${{ inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test \ - --grep "$base_pattern" - fi + echo "Running ${{ inputs.DEVICES_PER_TEST }} tests with ${{ inputs.WORKERS_COUNT }} workers" + + grep="(?=.*@${{ inputs.PLATFORM }})(?=.*@${{ inputs.DEVICES_PER_TEST }}-devices)" + [[ "${{ inputs.RISK }}" != "" ]] && grep="${grep}(?=.*@${{ inputs.RISK }})" + + _TESTING=${{ inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test --grep "$grep" - - name: Upload ${{ inputs.TEST_NAME }} results + - name: Upload ${{ inputs.DEVICES_PER_TEST }}-device test results uses: ./github/actions/upload-test-results with: PLATFORM: ${{ inputs.PLATFORM }} - UPLOAD_IDENTIFIER: ${{ inputs.UPLOAD_IDENTIFIER }} + UPLOAD_IDENTIFIER: devices-${{ inputs.DEVICES_PER_TEST }}-test-run \ No newline at end of file From df4846b97c7b3c65a3c4287df09d117ffbd96213 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 11:31:19 +1000 Subject: [PATCH 18/30] chore: update env.sample --- .env.sample | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/.env.sample b/.env.sample index eeee7ecf7..038797c1f 100644 --- a/.env.sample +++ b/.env.sample @@ -1,27 +1,32 @@ # this is a sample .env file. Copy it to .env, and edit the .env file to match your needs. +# Test binaries ANDROID_APK=/home/yougotthis/Downloads/session-android-universal.apk IOS_APP_PATH_PREFIX=/home/yougotthis/Downloads/Session.app -SDK_MANAGER_FULL_PATH=/home/yougotthis/Android/Sdk/cmdline-tools/latest/bin/sdkmanager -AVD_MANAGER_FULL_PATH=/home/yougotthis/Android/Sdk/cmdline-tools/latest/bin/avdmanager -EMULATOR_FULL_PATH=/home/yougotthis/Android/Sdk/emulator/emulator + +# Environment variables +ANDROID_SDK_ROOT=/home/yougotthis/Android/Sdk 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 + +# Device targets (required even if only running Android tests, leave it as a placeholder) +IOS_1_SIMULATOR=udid_of_simulator +IOS_2_SIMULATOR=udid_of_simulator +IOS_3_SIMULATOR=udid_of_simulator +IOS_4_SIMULATOR=udid_of_simulator +IOS_5_SIMULATOR=udid_of_simulator +IOS_6_SIMULATOR=udid_of_simulator +IOS_7_SIMULATOR=udid_of_simulator +IOS_8_SIMULATOR=udid_of_simulator +IOS_9_SIMULATOR=udid_of_simulator +IOS_10_SIMULATOR=udid_of_simulator +IOS_11_SIMULATOR=udid_of_simulator +IOS_12_SIMULATOR=udid_of_simulator + +# Playwright and reporting +PRINT_ONGOING_TEST_LOGS=1 PRINT_FAILED_TEST_LOGS=1 PLAYWRIGHT_RETRIES_COUNT=0 PLAYWRIGHT_REPEAT_COUNT=0 -PLAYWRIGHT_WORKERS_COUNT=1 \ No newline at end of file +PLAYWRIGHT_WORKERS_COUNT=1 +CI=0 +ALLURE_ENABLED='false' \ No newline at end of file From 4ffbd85923d4bf5d2bbc57a1fe12c080ccf586c0 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 11:33:48 +1000 Subject: [PATCH 19/30] fix: add back check if tests failed --- .github/workflows/android-regression.yml | 7 +++++++ github/actions/run-tests/action.yml | 7 ++++++- run/test/specs/utils/binaries.ts | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 42035fa3b..8e7bfc16e 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -247,3 +247,10 @@ jobs: run: | source ./scripts/ci.sh killall_emulators + + - name: Check if any tests failed + if: | + steps.test-1-device.outputs.test-result == 'failure' || + steps.test-2-device.outputs.test-result == 'failure' || + steps.test-3-device.outputs.test-result == 'failure' + run: echo 'Tests failed'; exit 1 diff --git a/github/actions/run-tests/action.yml b/github/actions/run-tests/action.yml index 0d4325a6f..0f0e705b8 100644 --- a/github/actions/run-tests/action.yml +++ b/github/actions/run-tests/action.yml @@ -40,4 +40,9 @@ runs: uses: ./github/actions/upload-test-results with: PLATFORM: ${{ inputs.PLATFORM }} - UPLOAD_IDENTIFIER: devices-${{ inputs.DEVICES_PER_TEST }}-test-run \ No newline at end of file + UPLOAD_IDENTIFIER: devices-${{ inputs.DEVICES_PER_TEST }}-test-run + +outputs: + test-result: + description: 'Test result (success/failure)' + value: ${{ steps.run-tests.outcome }} diff --git a/run/test/specs/utils/binaries.ts b/run/test/specs/utils/binaries.ts index 9460305f3..cbdebae1f 100644 --- a/run/test/specs/utils/binaries.ts +++ b/run/test/specs/utils/binaries.ts @@ -4,7 +4,7 @@ import * as path from 'path'; function existsAndFileOrThrow(path: string, id: string) { if (!existsSync(path) || !lstatSync(path).isFile()) { - throw new Error(`"${id}" does not exist at: ${path} or not a path`); + throw new Error(`"${id}" not found or not a file at: ${path}`); } } From b26477e5619a87179f53cb8cc9f2d9e27c21839b Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 11:37:46 +1000 Subject: [PATCH 20/30] chore: linting --- .github/workflows/android-regression.yml | 27 +++++++++---------- github/actions/run-tests/action.yml | 4 +-- .../specs/linked_device_create_group.spec.ts | 2 +- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 8e7bfc16e..59f25c40c 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -4,11 +4,11 @@ run-name: '${{ inputs.RISK }} regressions on ${{ github.head_ref || github.ref } on: workflow_dispatch: inputs: - RUNNER: + RUNNER: description: 'Which runner to use' required: true - type: choice - options: + type: choice + options: - 'linux' - 'mac' default: 'linux' @@ -69,7 +69,7 @@ jobs: EMULATOR_COUNT: ${{ github.event.inputs.RUNNER == 'mac' && '6' || '4' }} # macOS runner can support 6 emulators, linux runner only 4 WORKERS_1_DEVICE: ${{ github.event.inputs.RUNNER == 'mac' && '6' || '4' }} # 6 or 4 workers, 1 device each WORKERS_2_DEVICE: ${{ github.event.inputs.RUNNER == 'mac' && '3' || '2' }} # 3 or 2 workers, 2 devices each - WORKERS_3_DEVICE: ${{ github.event.inputs.RUNNER == 'mac' && '2' || '1' }} # 2 or 1 worker(s), 3 devices each + WORKERS_3_DEVICE: ${{ github.event.inputs.RUNNER == 'mac' && '2' || '1' }} # 2 or 1 worker(s), 3 devices each APK_URL: ${{ github.event.inputs.APK_URL }} BUILD_NUMBER: ${{ github.event.inputs.BUILD_NUMBER }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -99,17 +99,17 @@ jobs: with: lfs: true - # This is necessary because the macOS runner was not always pulling the lfs files despite checkout with lfs: true - # Check if LFS files were actually pulled - - name: Verify LFS files + # This is necessary because the macOS runner was not always pulling the lfs files despite checkout with lfs: true + # Check if LFS files were actually pulled + - name: Verify LFS files run: | echo "Ensuring all LFS screenshot files are downloaded..." git lfs pull --include="run/screenshots/**/*.png" - + echo "=== Verifying screenshot files ===" corrupted_files=0 total_files=0 - + for file in run/screenshots/**/*.png; do if [[ -f "$file" ]]; then # Make sure file exists (in case glob doesn't match anything) ((total_files++)) @@ -123,9 +123,9 @@ jobs: fi fi done - + echo "📊 Screenshot verification: $((total_files - corrupted_files))/$total_files files valid" - + if [ $corrupted_files -gt 0 ]; then echo "⚠️ Found $corrupted_files corrupted screenshot files" echo "📝 Tests using these files will be skipped or may fail" @@ -180,7 +180,7 @@ jobs: - name: Start emulators shell: bash - run: | # macOS can handle cold boot, linux cannot + run: | # macOS can handle cold boot, linux cannot source ./scripts/ci.sh if [[ "${{ github.event.inputs.RUNNER }}" == "mac" ]]; then start_for_snapshots @@ -239,7 +239,6 @@ jobs: with: PLATFORM: ${{ env.PLATFORM }} - - name: Stop emulators if: always() continue-on-error: true # just so we don't fail @@ -247,7 +246,7 @@ jobs: run: | source ./scripts/ci.sh killall_emulators - + - name: Check if any tests failed if: | steps.test-1-device.outputs.test-result == 'failure' || diff --git a/github/actions/run-tests/action.yml b/github/actions/run-tests/action.yml index 0f0e705b8..da1ffe623 100644 --- a/github/actions/run-tests/action.yml +++ b/github/actions/run-tests/action.yml @@ -30,10 +30,10 @@ runs: DEVICES_PER_TEST_COUNT: ${{ inputs.DEVICES_PER_TEST }} run: | echo "Running ${{ inputs.DEVICES_PER_TEST }} tests with ${{ inputs.WORKERS_COUNT }} workers" - + grep="(?=.*@${{ inputs.PLATFORM }})(?=.*@${{ inputs.DEVICES_PER_TEST }}-devices)" [[ "${{ inputs.RISK }}" != "" ]] && grep="${grep}(?=.*@${{ inputs.RISK }})" - + _TESTING=${{ inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test --grep "$grep" - name: Upload ${{ inputs.DEVICES_PER_TEST }}-device test results diff --git a/run/test/specs/linked_device_create_group.spec.ts b/run/test/specs/linked_device_create_group.spec.ts index 6177126a3..314ea0664 100644 --- a/run/test/specs/linked_device_create_group.spec.ts +++ b/run/test/specs/linked_device_create_group.spec.ts @@ -65,4 +65,4 @@ async function linkedGroup(platform: SupportedPlatformsType, testInfo: TestInfo) [alice2, bob1].map(device => device.waitForControlMessageToBePresent(groupNameNew)) ); await closeApp(alice1, alice2, bob1); -} \ No newline at end of file +} From 97ec28985d5fb552190986175f4bef01f9b1b46a Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 13:45:05 +1000 Subject: [PATCH 21/30] chore: update README.md --- README.md | 168 +++++++++++++++++++++++++++--------------------------- 1 file changed, 83 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 2328031d8..f157e70ad 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,91 @@ -# Automation testing for Session - -This repository holds the code to do integration tests with Appium and Session on iOS and Android. - -# Setup - -## Android SDK & Emulators - -First, you need to download android studio at https://developer.android.com/studio. - -Once installed, run it, open the SDK Manager and install the latest SDK tools. - -Once this is done, open up the AVD Manager, click on "Create Device" -> "Pixel 6" -> Next -> Select the System Image you want (I did my tests with **UpsideDownCake**), install it, select it, "Next" and "Finish". - -Then, create a second emulator following the exact same steps (the tests need 2 different emulators to run). - -Once done, you should be able to start each emulators and have them running at the same time. They will need to be running for the tests to work, because Appium won't start them. - -## Environment variables needed - -Before you can start the tests, you need to setup some environment variables. See the file .env.sample for an example. - -#### ANDROID_SDK_ROOT - -`ANDROID_SDK_ROOT` should point to the folder containing the sdks, so the folder containing folders like `platform-tools`, `system-images`, etc... -`export ANDROID_SDK_ROOT=~/Android/Sdk` - -#### APPIUM_ANDROID_BINARIES_ROOT - -`APPIUM_ANDROID_BINARIES_ROOT` should point to the file containing the apks to install for testing (such as `session-1.18.2-x86.apk`) -`export APPIUM_ANDROID_BINARIES_ROOT=~/appium-binaries` - -#### APPIUM_ADB_FULL_PATH - -`APPIUM_ADB_FULL_PATH` should point to the binary of adb inside the ANDROID_SDK folder -`export APPIUM_ADB_FULL_PATH=~/Android/Sdk/platform-tools/adb` - -### Multiple adb binaries - -Having multiple adb on your system will make tests unreliable, because the server will be restarted by Appium. - -On linux, if running `which adb` does not point to the `adb` binary in the `ANDROID_SDK_ROOT` you will have issues. - -You can get rid of adb on linux by running - +# Automation Testing for Session + +Integration tests for Session app on iOS and Android using Playwright and Appium. + +## Quick Start + +1. **Install dependencies:** + ```bash + nvm use + npm install -g yarn + yarn install --immutable + ``` + +2. **Setup environment:** + ```bash + cp .env.sample .env + # Edit .env with your specific paths - see Environment Configuration below + ``` + + +3. **Run tests locally:** + ```bash + yarn start-server # Starts Appium server + yarn test # Run all tests + yarn test-android # Android tests only + yarn test-ios # iOS tests only + yarn test-one 'Test name' # Run specific test (both platforms) + yarn test-one 'Test name @android/@ios' # Run specific test on one platform + ``` + +## Local Development + +Note: The tests use devices with specific resolutions for visual regression testing - ensure you have these available (see below). + +### Android + +Prerequisites: Android Studio installed with SDK tools +1. Create Pixel 6 emulators via AVD Manager (minimum 3 emulators - tests require up to 3 devices simultaneously) + - Recommended system image is Android API 34 with Google Play services +2. Download Session binaries from [the build repository](https://oxen.rocks) + - Choose the appropriate binary based on your network access: + - QA: Works on general networks + - AutomaticQA: Requires local devnet access +3. **Start emulators manually** - they need to be running before tests start (Appium won't launch them automatically) + +### iOS +Prerequisites: Xcode installed with minimum 3 iPhone 16 Pro Max simulators. The recommended Simulator runtime is iOS 18.3 or higher +1. Download Session binaries from [the build repository](https://oxen.rocks) +2. Extract .app file for Appium testing: + - If using pre-built binaries from the CI, use the .app directly + - Copy Session.app to an easily accessible location +3. Get iOS simulator UUIDs: + ```bash + xcrun simctl list devices | grep "iPhone 16 Pro Max" + ``` +4. Update environment configuration with path to Session.app and device UUIDs + +### Environment Configuration + +Copy `.env.sample` to `.env` and configure the following: + +**Required paths:** +```bash +ANDROID_SDK_ROOT=/path/to/Android/Sdk # SDK tools auto-discovered from here +ANDROID_APK=/path/to/session-android.apk # Android APK for testing +IOS_APP_PATH_PREFIX=/path/to/Session.app # iOS app for testing ``` -sudo apt remove adb -sudo apt remove android-tools-adb -``` - -`which adb` should not return anything. - -Somehow, Appium asks for the sdk tools but do not force the adb binary to come from the sdk tools folder. Making sure that there is no adb in your path should solve this. - -## Running tests on iOS Emulators - -First you need to get correct branch of Session that you want to test from Github. See [(https://github.com/session-foundation/session-ios/releases/)] and download the latest **ipa** under **Assets** - -Then to access the **.app** file that Appium needs for testing you need to build in Xcode and then find .app in your **Derived Data** folder for Xcode. -For Mac users this file will exist in: - -Macintosh HD > Username > Library > Developer > Xcode > Derived Data > (Then there will be a version of Session with a very long line of letters) > Build > Products > App store-iphonesimulator > Session.app - -Then Copy and Paste then app file onto Desktop (or anywhere you can access easily) then each time you build, navigate back to the file in Derived Data and copy and paste back to Desktop. -Then set the path to Session.app in your ios capabilities file. - -## Appium & tests setup - -First, install nvm for your system (https://github.com/nvm-sh/nvm). -For windows, head here: https://github.com/coreybutler/nvm-windows -For Mac, https://github.com/nvm-sh/nvm - -``` -nvm install #install node version from the .nvmrc file, currently v16.13.0 -nvm use # use that same node version, currently v16.13.0 -npm install -g yarn -yarn install --frozen # to install packages referenced from yarn.lock +**Test configuration:** +```bash +PLAYWRIGHT_RETRIES_COUNT=0 # Test retry attempts +PLAYWRIGHT_WORKERS_COUNT=1 # Parallel test workers +CI=0 # Set to 1 to simulate CI (mostly for Allure reporting) +ALLURE_ENABLED='false' # Set to 'true' to generate Allure reports ``` -Then, choose an option: +### Multiple ADB Binaries Warning +Having multiple adb installations can cause test instability. On Linux, ensure only the SDK version is available: + +```bash +sudo apt remove adb android-tools-adb +which adb # Should return nothing ``` -yarn tsc # Build typescript files -yarn run test # Run all the tests -Platform specific -yarn run test-android # To run just Android tests -yarn run test-ios # To run just iOS tests +## Test Organization -yarn run test-one 'Name of test' # To run one test (on both platforms) -yarn run test-one 'Name of test android/ios' # To run one test on either platform -``` +Tests are tagged with device requirements and risk levels: +- `@N-devices` - N-device tests (N = 1 || 2 || 3) +- `@high-risk`, `@medium-risk`, `@low-risk` - Risk-based test categorization +- `@android`, `@ios` - Platform-specific tests \ No newline at end of file From 437ac0969093a5654ce359009a605a31e3f142ca Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 13:47:26 +1000 Subject: [PATCH 22/30] chore: update Readme.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f157e70ad..bf0ac013f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Integration tests for Session app on iOS and Android using Playwright and Appium yarn test-android # Android tests only yarn test-ios # iOS tests only yarn test-one 'Test name' # Run specific test (both platforms) - yarn test-one 'Test name @android/@ios' # Run specific test on one platform + yarn test-one 'Test name @android' # Run specific test on one platform ``` ## Local Development @@ -71,7 +71,7 @@ IOS_APP_PATH_PREFIX=/path/to/Session.app # iOS app for testing PLAYWRIGHT_RETRIES_COUNT=0 # Test retry attempts PLAYWRIGHT_WORKERS_COUNT=1 # Parallel test workers CI=0 # Set to 1 to simulate CI (mostly for Allure reporting) -ALLURE_ENABLED='false' # Set to 'true' to generate Allure reports +ALLURE_ENABLED='false' # Set to 'true' to generate Allure reports (in conjunction with CI=1) ``` ### Multiple ADB Binaries Warning From 16555f1605b37bfe43e87709ff46767d0d764a48 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 15:04:16 +1000 Subject: [PATCH 23/30] fix: patch failing tests on macos emulators --- run/test/specs/review_triggers.spec.ts | 2 +- run/test/specs/utils/utilities.ts | 3 ++- run/types/DeviceWrapper.ts | 11 +++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/run/test/specs/review_triggers.spec.ts b/run/test/specs/review_triggers.spec.ts index b19e91022..82ba29e57 100644 --- a/run/test/specs/review_triggers.spec.ts +++ b/run/test/specs/review_triggers.spec.ts @@ -71,7 +71,7 @@ for (const { titleSnippet, descriptionSnippet, testStepName, trigger } of review await test.step(testStepName, async () => { await device.clickOnElementAll(new UserSettings(device)); await trigger(device); - await device.back(); + await device.navigateBack(); }); await test.step(TestSteps.VERIFY.SPECIFIC_MODAL('App Review'), async () => { await device.checkModalStrings( diff --git a/run/test/specs/utils/utilities.ts b/run/test/specs/utils/utilities.ts index b89a2464b..236a30a2c 100644 --- a/run/test/specs/utils/utilities.ts +++ b/run/test/specs/utils/utilities.ts @@ -20,7 +20,8 @@ export async function runScriptAndLog(toRun: string, verbose = false): Promise Date: Mon, 15 Sep 2025 15:15:56 +1000 Subject: [PATCH 24/30] fix: bump looksSame tolerance --- run/test/specs/utils/verify_screenshots.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/run/test/specs/utils/verify_screenshots.ts b/run/test/specs/utils/verify_screenshots.ts index 1727ff662..afcd0056f 100644 --- a/run/test/specs/utils/verify_screenshots.ts +++ b/run/test/specs/utils/verify_screenshots.ts @@ -86,6 +86,7 @@ export async function verifyElementScreenshot< // Use looks-same to verify the element screenshot against the baseline const { equal, diffImage } = await looksSame(elementScreenshotPath, baselineScreenshotPath, { createDiffImage: true, + tolerance: 5 }); if (!equal) { @@ -127,7 +128,8 @@ export async function verifyElementScreenshot< contentType: 'image/png', }, ]); - throw new Error(`The images do not match. The diff has been saved to ${diffImagePath}`); + console.log(`Visual comparison failed. The diff has been saved to ${diffImagePath}`) + throw new Error(`The UI doesn't match expected appearance`); } // Cleanup of element screenshot file on success From ea203753b57df6e3fbb71542f0446bdc1cf9b2de Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 15:29:47 +1000 Subject: [PATCH 25/30] handle API 34 and 35 Photos app --- run/test/specs/utils/handle_first_open.ts | 13 ++++++++++++- run/types/DeviceWrapper.ts | 1 - 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/run/test/specs/utils/handle_first_open.ts b/run/test/specs/utils/handle_first_open.ts index 46cf09be5..f56c04e21 100644 --- a/run/test/specs/utils/handle_first_open.ts +++ b/run/test/specs/utils/handle_first_open.ts @@ -36,11 +36,22 @@ export async function handlePhotosFirstTimeOpen(device: DeviceWrapper) { } // On Android, the Photos app shows a sign-in prompt the first time it's opened that needs to be dismissed if (device.isAndroid()) { - const signInButton = await device.doesElementExist({ + let signInButton = null; + // API 34 + signInButton = await device.doesElementExist({ strategy: 'id', selector: 'com.google.android.apps.photos:id/sign_in_button', maxWait: 2_000, }); + + if (!signInButton) { + // API 35 + signInButton = await device.doesElementExist({ + strategy: '-android uiautomator', + selector: 'new UiSelector().text("Sign in")', + maxWait: 2_000, + }); + } if (!signInButton) { device.log(`Photos app opened without a sign-in prompt, proceeding`); } else { diff --git a/run/types/DeviceWrapper.ts b/run/types/DeviceWrapper.ts index bad8f936c..b1853d606 100644 --- a/run/types/DeviceWrapper.ts +++ b/run/types/DeviceWrapper.ts @@ -16,7 +16,6 @@ import { describeLocator, DownloadMediaButton, FirstGif, - ImageName, ImagePermissionsModalAllow, LocatorsInterface, ReadReceiptsButton, From 7b60df35d90290c5f89aabf8129b129de3bb636e Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 16:10:18 +1000 Subject: [PATCH 26/30] fix: re-add failed tests check --- .github/workflows/android-regression.yml | 37 +++++++++++++++++++----- github/actions/run-tests/action.yml | 7 ----- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 59f25c40c..5fce8ff12 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -195,6 +195,7 @@ jobs: RISK: ${{ github.event.inputs.RISK }} - name: Run 1-device tests + id: test-1-device uses: ./github/actions/run-tests with: PLATFORM: ${{ env.PLATFORM }} @@ -202,8 +203,15 @@ jobs: WORKERS_COUNT: ${{ env.WORKERS_1_DEVICE }} DEVICES_PER_TEST: '1' HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} - + + - name: Upload 1-device test results + uses: ./github/actions/upload-test-results + with: + PLATFORM: ${{ env.PLATFORM }} + UPLOAD_IDENTIFIER: devices-1-test-run + - name: Run 2-device tests + id: test-2-device uses: ./github/actions/run-tests with: PLATFORM: ${{ env.PLATFORM }} @@ -212,7 +220,14 @@ jobs: DEVICES_PER_TEST: '2' HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} + - name: Upload 2-device test results + uses: ./github/actions/upload-test-results + with: + PLATFORM: ${{ env.PLATFORM }} + UPLOAD_IDENTIFIER: devices-2-test-run + - name: Run 3-device tests + id: test-3-device uses: ./github/actions/run-tests with: PLATFORM: ${{ env.PLATFORM }} @@ -221,6 +236,12 @@ jobs: DEVICES_PER_TEST: '3' HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} + - name: Upload 3-device test results + uses: ./github/actions/upload-test-results + with: + PLATFORM: ${{ env.PLATFORM }} + UPLOAD_IDENTIFIER: devices-3-test-run + - name: Generate and publish test report uses: ./github/actions/generate-publish-test-report if: ${{ always() && env.ALLURE_ENABLED == 'true' }} @@ -238,6 +259,13 @@ jobs: if: always() with: PLATFORM: ${{ env.PLATFORM }} + + - name: Check if any tests failed + if: | + steps.test-1-device.outputs.test-result == 'failure' || + steps.test-2-device.outputs.test-result == 'failure' || + steps.test-3-device.outputs.test-result == 'failure' + run: echo 'Tests failed'; exit 1 - name: Stop emulators if: always() @@ -247,9 +275,4 @@ jobs: source ./scripts/ci.sh killall_emulators - - name: Check if any tests failed - if: | - steps.test-1-device.outputs.test-result == 'failure' || - steps.test-2-device.outputs.test-result == 'failure' || - steps.test-3-device.outputs.test-result == 'failure' - run: echo 'Tests failed'; exit 1 + diff --git a/github/actions/run-tests/action.yml b/github/actions/run-tests/action.yml index da1ffe623..2324528f0 100644 --- a/github/actions/run-tests/action.yml +++ b/github/actions/run-tests/action.yml @@ -23,7 +23,6 @@ runs: steps: - name: Run ${{ inputs.DEVICES_PER_TEST }}-device tests id: run-tests - continue-on-error: true shell: bash env: PLAYWRIGHT_WORKERS_COUNT: ${{ inputs.WORKERS_COUNT }} @@ -36,12 +35,6 @@ runs: _TESTING=${{ inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test --grep "$grep" - - name: Upload ${{ inputs.DEVICES_PER_TEST }}-device test results - uses: ./github/actions/upload-test-results - with: - PLATFORM: ${{ inputs.PLATFORM }} - UPLOAD_IDENTIFIER: devices-${{ inputs.DEVICES_PER_TEST }}-test-run - outputs: test-result: description: 'Test result (success/failure)' From 1b8d2d6c7dd2c61ba2978d2f11163b2e8ca72cbe Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 16:14:12 +1000 Subject: [PATCH 27/30] chore: linting --- .github/workflows/android-regression.yml | 8 +++----- run/test/specs/linked_device_restore_group.spec.ts | 1 - run/test/specs/utils/handle_first_open.ts | 14 +++++++------- run/test/specs/utils/verify_screenshots.ts | 4 ++-- run/types/DeviceWrapper.ts | 6 +++--- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/.github/workflows/android-regression.yml b/.github/workflows/android-regression.yml index 5fce8ff12..bff8b18f6 100644 --- a/.github/workflows/android-regression.yml +++ b/.github/workflows/android-regression.yml @@ -203,13 +203,13 @@ jobs: WORKERS_COUNT: ${{ env.WORKERS_1_DEVICE }} DEVICES_PER_TEST: '1' HIDE_WEBDRIVER_LOGS: ${{ env.HIDE_WEBDRIVER_LOGS }} - + - name: Upload 1-device test results uses: ./github/actions/upload-test-results with: PLATFORM: ${{ env.PLATFORM }} UPLOAD_IDENTIFIER: devices-1-test-run - + - name: Run 2-device tests id: test-2-device uses: ./github/actions/run-tests @@ -259,7 +259,7 @@ jobs: if: always() with: PLATFORM: ${{ env.PLATFORM }} - + - name: Check if any tests failed if: | steps.test-1-device.outputs.test-result == 'failure' || @@ -274,5 +274,3 @@ jobs: run: | source ./scripts/ci.sh killall_emulators - - diff --git a/run/test/specs/linked_device_restore_group.spec.ts b/run/test/specs/linked_device_restore_group.spec.ts index 70f1ddea6..99de1db93 100644 --- a/run/test/specs/linked_device_restore_group.spec.ts +++ b/run/test/specs/linked_device_restore_group.spec.ts @@ -1,7 +1,6 @@ import type { TestInfo } from '@playwright/test'; import { bothPlatformsIt } from '../../types/sessionIt'; -import { USERNAME } from '../../types/testing'; import { ConversationHeaderName, MessageBody } from './locators/conversation'; import { ConversationItem } from './locators/home'; import { open_Alice1_Bob1_friends_group_Unknown1 } from './state_builder'; diff --git a/run/test/specs/utils/handle_first_open.ts b/run/test/specs/utils/handle_first_open.ts index f56c04e21..1dddd33a0 100644 --- a/run/test/specs/utils/handle_first_open.ts +++ b/run/test/specs/utils/handle_first_open.ts @@ -45,13 +45,13 @@ export async function handlePhotosFirstTimeOpen(device: DeviceWrapper) { }); if (!signInButton) { - // API 35 - signInButton = await device.doesElementExist({ - strategy: '-android uiautomator', - selector: 'new UiSelector().text("Sign in")', - maxWait: 2_000, - }); - } + // API 35 + signInButton = await device.doesElementExist({ + strategy: '-android uiautomator', + selector: 'new UiSelector().text("Sign in")', + maxWait: 2_000, + }); + } if (!signInButton) { device.log(`Photos app opened without a sign-in prompt, proceeding`); } else { diff --git a/run/test/specs/utils/verify_screenshots.ts b/run/test/specs/utils/verify_screenshots.ts index afcd0056f..631f49826 100644 --- a/run/test/specs/utils/verify_screenshots.ts +++ b/run/test/specs/utils/verify_screenshots.ts @@ -86,7 +86,7 @@ export async function verifyElementScreenshot< // Use looks-same to verify the element screenshot against the baseline const { equal, diffImage } = await looksSame(elementScreenshotPath, baselineScreenshotPath, { createDiffImage: true, - tolerance: 5 + tolerance: 5, }); if (!equal) { @@ -128,7 +128,7 @@ export async function verifyElementScreenshot< contentType: 'image/png', }, ]); - console.log(`Visual comparison failed. The diff has been saved to ${diffImagePath}`) + console.log(`Visual comparison failed. The diff has been saved to ${diffImagePath}`); throw new Error(`The UI doesn't match expected appearance`); } diff --git a/run/types/DeviceWrapper.ts b/run/types/DeviceWrapper.ts index b1853d606..0feebbf0c 100644 --- a/run/types/DeviceWrapper.ts +++ b/run/types/DeviceWrapper.ts @@ -330,7 +330,7 @@ export class DeviceWrapper { const blacklist = [ { from: 'Voice message', to: 'New voice message' }, - { from: 'url_bar', to: 'status_bar'} + { from: 'url_bar', to: 'status_bar' }, ]; // System locators such as 'network.loki.messenger.qa:id' can cause false positives with too high similarity scores @@ -2111,9 +2111,9 @@ export class DeviceWrapper { }); await sleepFor(500); await this.matchAndTapImage( - {strategy: 'xpath', selector: '//*[starts-with(@content-desc, "Photo taken on")]'}, + { strategy: 'xpath', selector: '//*[starts-with(@content-desc, "Photo taken on")]' }, profilePicture - ) + ); // await this.clickOnElementAll(new ImageName(this)); await this.clickOnElementById('network.loki.messenger.qa:id/crop_image_menu_crop'); } From 606f95a8703589fc2e2e68770157e644360ed4b2 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Mon, 15 Sep 2025 16:58:23 +1000 Subject: [PATCH 28/30] fix: review tests close settings now --- run/test/specs/review_negative.spec.ts | 3 ++- run/test/specs/review_once.spec.ts | 3 ++- run/test/specs/review_positive.spec.ts | 3 ++- run/test/specs/review_triggers.spec.ts | 3 ++- run/types/DeviceWrapper.ts | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/run/test/specs/review_negative.spec.ts b/run/test/specs/review_negative.spec.ts index 63c6c88fc..f35e2eab9 100644 --- a/run/test/specs/review_negative.spec.ts +++ b/run/test/specs/review_negative.spec.ts @@ -4,6 +4,7 @@ import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { TestSteps } from '../../types/allure'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; +import { CloseSettings } from './locators'; import { ReviewPromptNeedsWorkButton, ReviewPromptNotNowButton, @@ -41,7 +42,7 @@ async function reviewPromptNegative(platform: SupportedPlatformsType, testInfo: await test.step(TestSteps.OPEN.PATH, async () => { await device.clickOnElementAll(new PathMenuItem(device)); await device.back(); - await device.back(); + await device.clickOnElementAll(new CloseSettings(device)) }); await test.step(TestSteps.VERIFY.SPECIFIC_MODAL('Enjoying Session'), async () => { await device.checkModalStrings( diff --git a/run/test/specs/review_once.spec.ts b/run/test/specs/review_once.spec.ts index 42fb61626..dc138051e 100644 --- a/run/test/specs/review_once.spec.ts +++ b/run/test/specs/review_once.spec.ts @@ -4,6 +4,7 @@ import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { TestSteps } from '../../types/allure'; import { androidIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; +import { CloseSettings } from './locators'; import { ModalHeading } from './locators/global'; import { PlusButton } from './locators/home'; import { PathMenuItem, UserSettings } from './locators/settings'; @@ -32,7 +33,7 @@ async function reviewPromptOnce(platform: SupportedPlatformsType, testInfo: Test await device.clickOnElementAll(new UserSettings(device)); await device.clickOnElementAll(new PathMenuItem(device)); await device.back(); - await device.back(); + await device.clickOnElementAll(new CloseSettings(device)); }); await test.step(TestSteps.VERIFY.GENERIC_MODAL, async () => { await device.checkModalStrings( diff --git a/run/test/specs/review_positive.spec.ts b/run/test/specs/review_positive.spec.ts index 7432b7439..6ce4dcfd1 100644 --- a/run/test/specs/review_positive.spec.ts +++ b/run/test/specs/review_positive.spec.ts @@ -4,6 +4,7 @@ import { englishStrippedStr } from '../../localizer/englishStrippedStr'; import { TestSteps } from '../../types/allure'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; +import { CloseSettings } from './locators'; import { ReviewPromptItsGreatButton, ReviewPromptNotNowButton, @@ -37,7 +38,7 @@ async function reviewPromptPositive(platform: SupportedPlatformsType, testInfo: await device.clickOnElementAll(new UserSettings(device)); await device.clickOnElementAll(new PathMenuItem(device)); await device.back(); - await device.back(); + await device.clickOnElementAll(new CloseSettings(device)); }); await test.step(TestSteps.VERIFY.SPECIFIC_MODAL('Enjoying Session'), async () => { await device.checkModalStrings( diff --git a/run/test/specs/review_triggers.spec.ts b/run/test/specs/review_triggers.spec.ts index 82ba29e57..cc2b1ad25 100644 --- a/run/test/specs/review_triggers.spec.ts +++ b/run/test/specs/review_triggers.spec.ts @@ -5,6 +5,7 @@ import { TestSteps } from '../../types/allure'; import { DeviceWrapper } from '../../types/DeviceWrapper'; import { bothPlatformsIt } from '../../types/sessionIt'; import { USERNAME } from '../../types/testing'; +import { CloseSettings } from './locators'; import { CopyURLButton } from './locators/global'; import { AppearanceMenuItem, @@ -71,7 +72,7 @@ for (const { titleSnippet, descriptionSnippet, testStepName, trigger } of review await test.step(testStepName, async () => { await device.clickOnElementAll(new UserSettings(device)); await trigger(device); - await device.navigateBack(); + await device.clickOnElementAll(new CloseSettings(device)); }); await test.step(TestSteps.VERIFY.SPECIFIC_MODAL('App Review'), async () => { await device.checkModalStrings( diff --git a/run/types/DeviceWrapper.ts b/run/types/DeviceWrapper.ts index 0feebbf0c..df351f361 100644 --- a/run/types/DeviceWrapper.ts +++ b/run/types/DeviceWrapper.ts @@ -568,7 +568,7 @@ export class DeviceWrapper { skipHealing: true, }); } catch (fallbackError) { - throw new Error(`Element ${primaryDescription} and ${fallbackDescription} not found.`); + throw new Error(`Element ${primaryDescription} or ${fallbackDescription} not found.`); } } } From 6780a8048ba3c2d0fc392e8618b46b51aef33fb7 Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Tue, 7 Oct 2025 16:22:13 +1100 Subject: [PATCH 29/30] fix: adjust action env --- github/actions/run-tests/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/github/actions/run-tests/action.yml b/github/actions/run-tests/action.yml index 2324528f0..f4ad5e868 100644 --- a/github/actions/run-tests/action.yml +++ b/github/actions/run-tests/action.yml @@ -14,9 +14,6 @@ inputs: DEVICES_PER_TEST: description: 'Number of devices per test' required: true - HIDE_WEBDRIVER_LOGS: - description: 'Hide webdriver logs (1 to hide, 0 to show)' - required: true runs: using: 'composite' @@ -27,13 +24,16 @@ runs: env: PLAYWRIGHT_WORKERS_COUNT: ${{ inputs.WORKERS_COUNT }} DEVICES_PER_TEST_COUNT: ${{ inputs.DEVICES_PER_TEST }} + _TESTING: ${{ env._TESTING }} + PRINT_FAILED_TEST_LOGS: ${{ env.PRINT_FAILED_TEST_LOGS }} + PRINT_ONGOING_TEST_LOGS: ${{ env.PRINT_ONGOING_TEST_LOGS }} run: | echo "Running ${{ inputs.DEVICES_PER_TEST }} tests with ${{ inputs.WORKERS_COUNT }} workers" grep="(?=.*@${{ inputs.PLATFORM }})(?=.*@${{ inputs.DEVICES_PER_TEST }}-devices)" [[ "${{ inputs.RISK }}" != "" ]] && grep="${grep}(?=.*@${{ inputs.RISK }})" - _TESTING=${{ inputs.HIDE_WEBDRIVER_LOGS }} npx playwright test --grep "$grep" + npx playwright test --grep "$grep" outputs: test-result: From 648128c6c69563a63dd8d8db5e2c6399372c068c Mon Sep 17 00:00:00 2001 From: Miklos Mandoki Date: Tue, 7 Oct 2025 17:05:48 +1100 Subject: [PATCH 30/30] chore: update readme.md --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index bf0ac013f..c3bd77a1b 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,24 @@ Integration tests for Session app on iOS and Android using Playwright and Appium yarn install --immutable ``` -2. **Setup environment:** +2. **Install Git LFS:** + ```bash + # macOS + brew install git-lfs + + # Linux + sudo apt install git-lfs + + # Then + git lfs install + git lfs pull # Ensure all LFS files are downloaded + ``` +3. **Setup environment:** ```bash cp .env.sample .env # Edit .env with your specific paths - see Environment Configuration below ``` - - -3. **Run tests locally:** +4. **Run tests locally:** ```bash yarn start-server # Starts Appium server yarn test # Run all tests @@ -34,26 +44,41 @@ Note: The tests use devices with specific resolutions for visual regression test ### Android -Prerequisites: Android Studio installed with SDK tools -1. Create Pixel 6 emulators via AVD Manager (minimum 3 emulators - tests require up to 3 devices simultaneously) +Prerequisites: Android Studio installed with SDK tools available +1. Create 3x Pixel 6 emulators via AVD Manager (minimum 3 emulators - tests require up to 3 devices simultaneously) - Recommended system image is Android API 34 with Google Play services 2. Download Session binaries from [the build repository](https://oxen.rocks) - Choose the appropriate binary based on your network access: - QA: Works on general networks - AutomaticQA: Requires local devnet access -3. **Start emulators manually** - they need to be running before tests start (Appium won't launch them automatically) +3. Set environment variable: + ```bash + # In your .env file + ANDROID_APK=/path/to/session-android.apk + ``` +4. Start emulators manually - they need to be running before tests start (Appium won't launch them automatically) + ```bash + emulator @ + ``` ### iOS -Prerequisites: Xcode installed with minimum 3 iPhone 16 Pro Max simulators. The recommended Simulator runtime is iOS 18.3 or higher -1. Download Session binaries from [the build repository](https://oxen.rocks) -2. Extract .app file for Appium testing: - - If using pre-built binaries from the CI, use the .app directly - - Copy Session.app to an easily accessible location -3. Get iOS simulator UUIDs: - ```bash - xcrun simctl list devices | grep "iPhone 16 Pro Max" +Prerequisites: Xcode installed + +1. Create iOS simulators with preloaded media attachments: + ```bash + # Local development (create 3 simulators to be able to run all tests) + yarn create-simulators 3 + # Or specify custom count + yarn create-simulators + ``` +2. Download Session binaries from the [the build repository](https://oxen.rocks) +3. Extract .app file and copy Session.app to an easily accessible location +4. Set environment variable: + + ```bash + # In your .env file + IOS_APP_PATH_PREFIX=/path/to/Session.app ``` -4. Update environment configuration with path to Session.app and device UUIDs ### Environment Configuration @@ -68,7 +93,9 @@ IOS_APP_PATH_PREFIX=/path/to/Session.app # iOS app for testing **Test configuration:** ```bash +_TESTING=1 # Skip printing appium/wdio logs PLAYWRIGHT_RETRIES_COUNT=0 # Test retry attempts +PLAYWRIGHT_REPEAT_COUNT=0 # Successful test repeat count PLAYWRIGHT_WORKERS_COUNT=1 # Parallel test workers CI=0 # Set to 1 to simulate CI (mostly for Allure reporting) ALLURE_ENABLED='false' # Set to 'true' to generate Allure reports (in conjunction with CI=1)