Move screenshot to iOSBridge
Summary: In order to support IOS cloud devices, we need to abstract over the direct uses of idb/xcrun so we can switch them out based on more than the device type. Note that there's a bit of a type weirdness in there. I'll clean this up with the next diff. Reviewed By: mweststrate Differential Revision: D30248036 fbshipit-source-id: ec8571429e04abe059850ef334a6645ae4a5e034
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f515df1c01
commit
52b3edc5ad
@@ -12,14 +12,10 @@ import child_process, {ChildProcess} from 'child_process';
|
|||||||
import BaseDevice from './BaseDevice';
|
import BaseDevice from './BaseDevice';
|
||||||
import JSONStream from 'JSONStream';
|
import JSONStream from 'JSONStream';
|
||||||
import {Transform} from 'stream';
|
import {Transform} from 'stream';
|
||||||
import fs from 'fs-extra';
|
|
||||||
import {v1 as uuid} from 'uuid';
|
|
||||||
import path from 'path';
|
|
||||||
import {exec} from 'promisify-child-process';
|
import {exec} from 'promisify-child-process';
|
||||||
import {default as promiseTimeout} from '../utils/promiseTimeout';
|
import {default as promiseTimeout} from '../utils/promiseTimeout';
|
||||||
import {IOSBridge} from '../utils/IOSBridge';
|
import {IOSBridge} from '../utils/IOSBridge';
|
||||||
import split2 from 'split2';
|
import split2 from 'split2';
|
||||||
import {getAppTempPath} from '../utils/pathUtils';
|
|
||||||
|
|
||||||
type IOSLogLevel = 'Default' | 'Info' | 'Debug' | 'Error' | 'Fault';
|
type IOSLogLevel = 'Default' | 'Info' | 'Debug' | 'Error' | 'Fault';
|
||||||
|
|
||||||
@@ -67,19 +63,8 @@ export default class IOSDevice extends BaseDevice {
|
|||||||
if (!this.connected.get()) {
|
if (!this.connected.get()) {
|
||||||
return Buffer.from([]);
|
return Buffer.from([]);
|
||||||
}
|
}
|
||||||
const tmpImageName = uuid() + '.png';
|
// HACK: Will restructure the types to allow for the ! to be removed.
|
||||||
const tmpDirectory = getAppTempPath();
|
return await this.iOSBridge.screenshot!(this.serial);
|
||||||
const tmpFilePath = path.join(tmpDirectory, tmpImageName);
|
|
||||||
const command =
|
|
||||||
this.deviceType === 'emulator'
|
|
||||||
? `xcrun simctl io ${this.serial} screenshot ${tmpFilePath}`
|
|
||||||
: `idb screenshot --udid ${this.serial} ${tmpFilePath}`;
|
|
||||||
return exec(command)
|
|
||||||
.then(() => fs.readFile(tmpFilePath))
|
|
||||||
.then(async (buffer) => {
|
|
||||||
await fs.unlink(tmpFilePath);
|
|
||||||
return buffer;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToLocation(location: string) {
|
navigateToLocation(location: string) {
|
||||||
|
|||||||
@@ -7,9 +7,13 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs-extra';
|
||||||
import child_process from 'child_process';
|
import child_process from 'child_process';
|
||||||
import {DeviceType} from 'flipper-plugin-lib';
|
import {DeviceType} from 'flipper-plugin-lib';
|
||||||
|
import {v1 as uuid} from 'uuid';
|
||||||
|
import path from 'path';
|
||||||
|
import {exec} from 'promisify-child-process';
|
||||||
|
import {getAppTempPath} from '../utils/pathUtils';
|
||||||
|
|
||||||
export interface IOSBridge {
|
export interface IOSBridge {
|
||||||
idbAvailable: boolean;
|
idbAvailable: boolean;
|
||||||
@@ -17,6 +21,7 @@ export interface IOSBridge {
|
|||||||
udid: string,
|
udid: string,
|
||||||
deviceType: DeviceType,
|
deviceType: DeviceType,
|
||||||
) => child_process.ChildProcessWithoutNullStreams;
|
) => child_process.ChildProcessWithoutNullStreams;
|
||||||
|
screenshot?: (serial: string) => Promise<Buffer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isAvailable(idbPath: string): Promise<boolean> {
|
async function isAvailable(idbPath: string): Promise<boolean> {
|
||||||
@@ -32,7 +37,8 @@ async function isAvailable(idbPath: string): Promise<boolean> {
|
|||||||
function getLogExtraArgs(deviceType: DeviceType) {
|
function getLogExtraArgs(deviceType: DeviceType) {
|
||||||
if (deviceType === 'physical') {
|
if (deviceType === 'physical') {
|
||||||
return [
|
return [
|
||||||
// idb has a --json option, but that doesn't actually work!
|
// idb has a --json option, but that doesn't actually work for physical
|
||||||
|
// devices!
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
@@ -77,6 +83,36 @@ export function xcrunStartLogListener(udid: string, deviceType: DeviceType) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeTempScreenshotFilePath() {
|
||||||
|
const imageName = uuid() + '.png';
|
||||||
|
const directory = getAppTempPath();
|
||||||
|
return path.join(directory, imageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runScreenshotCommand(
|
||||||
|
command: string,
|
||||||
|
imagePath: string,
|
||||||
|
): Promise<Buffer> {
|
||||||
|
return exec(command)
|
||||||
|
.then(() => fs.readFile(imagePath))
|
||||||
|
.then(async (buffer) => {
|
||||||
|
await fs.unlink(imagePath);
|
||||||
|
return buffer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function xcrunScreenshot(serial: string): Promise<Buffer> {
|
||||||
|
const imagePath = makeTempScreenshotFilePath();
|
||||||
|
const command = `xcrun simctl io ${serial} screenshot ${imagePath}`;
|
||||||
|
return runScreenshotCommand(command, imagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function idbScreenshot(serial: string): Promise<Buffer> {
|
||||||
|
const imagePath = makeTempScreenshotFilePath();
|
||||||
|
const command = `idb screenshot --udid ${serial} ${imagePath}`;
|
||||||
|
return runScreenshotCommand(command, imagePath);
|
||||||
|
}
|
||||||
|
|
||||||
export async function makeIOSBridge(
|
export async function makeIOSBridge(
|
||||||
idbPath: string,
|
idbPath: string,
|
||||||
isXcodeDetected: boolean,
|
isXcodeDetected: boolean,
|
||||||
@@ -87,6 +123,7 @@ export async function makeIOSBridge(
|
|||||||
return {
|
return {
|
||||||
idbAvailable: true,
|
idbAvailable: true,
|
||||||
startLogListener: idbStartLogListener.bind(null, idbPath),
|
startLogListener: idbStartLogListener.bind(null, idbPath),
|
||||||
|
screenshot: idbScreenshot,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +132,7 @@ export async function makeIOSBridge(
|
|||||||
return {
|
return {
|
||||||
idbAvailable: false,
|
idbAvailable: false,
|
||||||
startLogListener: xcrunStartLogListener,
|
startLogListener: xcrunStartLogListener,
|
||||||
|
screenshot: xcrunScreenshot,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// no idb, and not a simulator, we can't log this device
|
// no idb, and not a simulator, we can't log this device
|
||||||
|
|||||||
@@ -9,11 +9,15 @@
|
|||||||
|
|
||||||
import {makeIOSBridge} from '../IOSBridge';
|
import {makeIOSBridge} from '../IOSBridge';
|
||||||
import childProcess from 'child_process';
|
import childProcess from 'child_process';
|
||||||
|
import * as promisifyChildProcess from 'promisify-child-process';
|
||||||
import {mocked} from 'ts-jest/utils';
|
import {mocked} from 'ts-jest/utils';
|
||||||
|
|
||||||
jest.mock('child_process');
|
jest.mock('child_process');
|
||||||
const spawn = mocked(childProcess.spawn);
|
const spawn = mocked(childProcess.spawn);
|
||||||
|
|
||||||
|
jest.mock('promisify-child-process');
|
||||||
|
const exec = mocked(promisifyChildProcess.exec);
|
||||||
|
|
||||||
test('uses xcrun with no idb when xcode is detected', async () => {
|
test('uses xcrun with no idb when xcode is detected', async () => {
|
||||||
const ib = await makeIOSBridge('', true);
|
const ib = await makeIOSBridge('', true);
|
||||||
expect(ib.startLogListener).toBeDefined();
|
expect(ib.startLogListener).toBeDefined();
|
||||||
@@ -92,3 +96,23 @@ test('uses no log listener when xcode is not detected', async () => {
|
|||||||
const ib = await makeIOSBridge('', false);
|
const ib = await makeIOSBridge('', false);
|
||||||
expect(ib.startLogListener).toBeUndefined();
|
expect(ib.startLogListener).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('uses xcrun to take screenshots with no idb when xcode is detected', async () => {
|
||||||
|
const ib = await makeIOSBridge('', true);
|
||||||
|
|
||||||
|
ib.screenshot!('deadbeef');
|
||||||
|
|
||||||
|
expect(exec).toHaveBeenCalledWith(
|
||||||
|
'xcrun simctl io deadbeef screenshot /temp/00000000-0000-0000-0000-000000000000.png',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses idb to take screenshots when available', async () => {
|
||||||
|
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
||||||
|
|
||||||
|
ib.screenshot!('deadbeef');
|
||||||
|
|
||||||
|
expect(exec).toHaveBeenCalledWith(
|
||||||
|
'idb screenshot --udid deadbeef /temp/00000000-0000-0000-0000-000000000000.png',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user