Skip to content

iOS media handling changes #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"devDependencies": {
"@appium/execute-driver-plugin": "^3.0.1",
"@appium/images-plugin": "^3.0.17",
"@appium/opencv": "^3.0.9",
"@eslint/js": "^9.14.0",
"@types/lodash": "^4.14.191",
"@types/node": "^20.14.10",
Expand Down Expand Up @@ -61,6 +62,7 @@
"@appium/support@npm:^5.1.6": "patch:@appium/support@npm%3A5.1.2#~/patches/@appium-support-npm-5.1.2-0c5ea57d71.patch",
"@appium/support@npm:^5.1.2": "patch:@appium/support@npm%3A5.1.2#~/patches/@appium-support-npm-5.1.2-0c5ea57d71.patch",
"@appium/support@npm:^5.0.3": "patch:@appium/support@npm%3A5.1.2#~/patches/@appium-support-npm-5.1.2-0c5ea57d71.patch",
"@appium/support@npm:^5.1.1": "patch:@appium/support@npm%3A5.1.2#~/patches/@appium-support-npm-5.1.2-0c5ea57d71.patch"
"@appium/support@npm:^5.1.1": "patch:@appium/support@npm%3A5.1.2#~/patches/@appium-support-npm-5.1.2-0c5ea57d71.patch",
"sharp": "^0.34.1"
}
}
1 change: 1 addition & 0 deletions run/constants/testfiles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const testImage = 'test_image.jpg';
export const testFile = 'test_file.pdf';
export const testVideo = 'test_video.mp4';
export const testVideoThumbnail = 'test_video_thumbnail.png';
export const profilePicture = 'profile_picture.jpg';
13 changes: 13 additions & 0 deletions run/test/specs/locators/browsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,16 @@ export class SafariAddressBar extends LocatorsInterface {
}
}
}
export class SafariShareButton extends LocatorsInterface {
public build() {
switch (this.platform) {
case 'android':
throw new Error('Unsupported platform');
case 'ios':
return {
strategy: 'accessibility id',
selector: 'ShareButton',
} as const;
}
}
}
39 changes: 39 additions & 0 deletions run/test/specs/locators/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,42 @@ export class DisguisedApp extends LocatorsInterface {
} as const;
}
}
export class IOSSaveToFiles extends LocatorsInterface {
public build() {
switch (this.platform) {
case 'android':
throw new Error('Unsupported platform');
case 'ios':
return {
strategy: 'accessibility id',
selector: 'Save to Files',
} as const;
}
}
}
export class IOSSaveButton extends LocatorsInterface {
public build() {
switch (this.platform) {
case 'android':
throw new Error('Unsupported platform');
case 'ios':
return {
strategy: 'accessibility id',
selector: 'Save',
} as const;
}
}
}
export class IOSReplaceButton extends LocatorsInterface {
public build() {
switch (this.platform) {
case 'android':
throw new Error('Unsupported platform');
case 'ios':
return {
strategy: 'accessibility id',
selector: 'Replace',
} as const;
}
}
}
6 changes: 2 additions & 4 deletions run/test/specs/locators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,13 +592,11 @@ export class ImageName extends LocatorsInterface {
selector: `//*[starts-with(@content-desc, "Photo taken on")]`,
};
case 'ios':
return {
strategy: 'accessibility id',
selector: 'Photo, 19 March 2024, 5:13 pm',
};
throw new Error(`No such element on iOS`);
}
}
}

// TODO update StrategyExtractionObj to include Locator class
// export class PendingMessageRequestControlMessage extends LocatorsInterface {
// public build(): StrategyExtractionObj {
Expand Down
Binary file added run/test/specs/media/test_video_thumbnail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 8 additions & 35 deletions run/test/specs/user_actions_share_to_session.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { bothPlatformsIt } from '../../types/sessionIt';
import { USERNAME } from '../../types/testing';
import {
DownloadMediaButton,
ImageName,
MediaMessageInput,
SendMediaButton,
ShareExtensionIcon,
} from './locators';
import { ImageName, MediaMessageInput, SendMediaButton, ShareExtensionIcon } from './locators';
import { PhotoLibrary } from './locators/external';
import { open_Alice1_Bob1_friends } from './state_builder';
import { sleepFor } from './utils';
import { SupportedPlatformsType } from './utils/open_app';
import { testImage } from '../../constants/testfiles';

bothPlatformsIt({
title: 'Share to session',
Expand All @@ -27,40 +22,20 @@ async function shareToSession(platform: SupportedPlatformsType) {
focusFriendsConvo: true,
});
const testMessage = 'Testing sharing an image through photo gallery to Session';
const ronSwansonBirthday = '196705060700.00';
const fileName = 'test_image.jpg';

// Need to make sure contact is confirm before moving away from Session
await sleepFor(1000);
await alice1.pressHome();
await sleepFor(2000);
await alice1.pushMediaToDevice(testImage);
// Photo app is on different page than Session
await alice1.onIOS().swipeRightAny('Session');
await alice1.clickOnElementAll(new PhotoLibrary(alice1));
await sleepFor(2000);
let testImage;
if (platform === 'android') {
testImage = await alice1.doesElementExist({
...new ImageName(alice1).build(),
maxWait: 5000,
});
} else {
testImage = await alice1.doesElementExist({
strategy: 'xpath',
selector:
'//XCUIElementTypeImage[@name="PXGGridLayout-Info" and @label="Photo, 17 April, 9:56 am"]',
});
}
if (!testImage) {
await alice1.pushMediaToDevice(fileName, ronSwansonBirthday);
}
await alice1.onIOS().clickOnByAccessibilityID('Select');
await alice1
.onIOS()
.clickOnElementXPath(
'//XCUIElementTypeImage[@name="PXGGridLayout-Info" and @label="Photo, 17 April, 9:56 am"]',
1000
);
.matchAndTapImage({ strategy: 'xpath', selector: `//XCUIElementTypeImage` }, testImage);
await alice1.onAndroid().clickOnElementAll(new ImageName(alice1));
await alice1.clickOnElementAll({ strategy: 'accessibility id', selector: 'Share' });
await alice1.clickOnElementAll(new ShareExtensionIcon(alice1));
Expand All @@ -72,13 +47,11 @@ async function shareToSession(platform: SupportedPlatformsType) {
await alice1.inputText(testMessage, new MediaMessageInput(alice1));
await alice1.clickOnElementAll(new SendMediaButton(alice1));
// Loading screen...
// TODO: On iOS, reset Photos UI state for alice1 (e.g. deselect, back out) after test runs
// Currently skipping cleanup—future flake risk if Photos is left in selection mode
await alice1.waitForLoadingOnboarding();
// Check Bob's device
// TODO replace with TrustUser function when Groups are merged
await bob1.clickOnByAccessibilityID('Untrusted attachment message');
await sleepFor(500);
// User B - Click on 'download'
await bob1.clickOnElementAll(new DownloadMediaButton(bob1));
// Check Bob's device
await bob1.trustAttachments(USERNAME.ALICE);
await bob1.waitForTextElementToBePresent({
strategy: 'accessibility id',
selector: 'Message body',
Expand Down
64 changes: 64 additions & 0 deletions run/test/specs/utils/copy_file_to_simulator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as fs from 'fs';
import * as path from 'path';
import { execSync } from 'child_process';
import { DeviceWrapper } from '../../../types/DeviceWrapper';

const TARGET_GROUP_ID = 'group.com.apple.FileProvider.LocalStorage';
const MEDIA_ROOT = path.join('run', 'test', 'specs', 'media');

/**
* Utility for copying a file from the local 'media' directory to the current iOS simulator's
* "Downloads" folder (inside the Files app group container) on disk, if not already present.
*
* Motivation: 'xcrun simctl addmedia' does not support PDFs, and downloading files in tests is unreliable.
*/

/**
* Gets the group container path for the Files app on the simulator.
* Inspired by https://medium.com/@liwp.stephen/solution-how-to-get-files-in-files-app-in-ios-simulator-de1e9c9dc6fe
*/
function getFilesAppGroupContainerPath(udid: string): string {
const listAppsOutput = execSync(`xcrun simctl listapps ${udid}`, { encoding: 'utf8' });
const groupContainerRegex = (groupId: string) => new RegExp(`"${groupId}" = "file://([^"]+)"`);
const match = listAppsOutput.match(groupContainerRegex(TARGET_GROUP_ID));
if (!match?.[1]) {
throw new Error(`Group container for "${TARGET_GROUP_ID}" not found.`);
}
return match[1];
}

/**
* Gets the full destination path for the file in the simulator's Downloads folder.
*/
function getSimulatorDownloadsPath(
groupContainerPath: string,
filename: string
): { downloadsPath: string; destinationPath: string } {
const downloadsPath = path.join(groupContainerPath, 'File Provider Storage', 'Downloads');
const destinationPath = path.join(downloadsPath, filename);
return { downloadsPath, destinationPath };
}

/**
* Copies a file from the 'media' directory to the simulator's "Downloads" folder on disk if not already present.
*/
export function copyFileToSimulator(device: DeviceWrapper, fileName: string): void {
const sourcePath = path.join(MEDIA_ROOT, fileName);

const groupContainerPath = getFilesAppGroupContainerPath(device.udid);
const { downloadsPath, destinationPath } = getSimulatorDownloadsPath(
groupContainerPath,
fileName
);

if (fs.existsSync(destinationPath)) {
console.log(`File already exists in simulator: ${destinationPath}`);
return;
}
if (!fs.existsSync(sourcePath)) {
throw new Error(`Source file does not exist: ${sourcePath}`);
}
fs.mkdirSync(downloadsPath, { recursive: true });
fs.copyFileSync(sourcePath, destinationPath);
console.log(`Copied ${fileName} to simulator Downloads at: ${downloadsPath}`);
}
2 changes: 1 addition & 1 deletion run/test/specs/utils/open_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ const openiOSApp = async (

const capabilities = getIosCapabilities(actualCapabilitiesIndex as CapabilitiesIndexType);
const udid = capabilities.alwaysMatch['appium:udid'] as string;
// TODO bring in changes from QA-1265

const { device: wrappedDevice } = await cleanPermissions(opts, udid, capabilities);
return { device: wrappedDevice };
};
Expand Down
Loading