Skip to content
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

fix: handle async execute on iOS without awaitPromise #199

Merged
merged 3 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
33 changes: 22 additions & 11 deletions lib/remote-debugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,14 +422,14 @@ class RemoteDebugger extends events.EventEmitter {
// first create a Promise on the page, saving the resolve/reject functions
// as properties
const promiseName = `appiumAsyncExecutePromise${UUID.create().toString().replace(/-/g, '')}`;
let script = `var res, rej;` +
`window.${promiseName} = new Promise(function (resolve, reject) {` +
` res = resolve;` +
` rej = reject;` +
`});` +
`window.${promiseName}.resolve = res;` +
`window.${promiseName}.reject = rej;` +
`window.${promiseName};`;
const script = `var res, rej;` +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say quotes also support multiline strings, so pluses are redendant

`window.${promiseName} = new Promise(function (resolve, reject) {` +
` res = resolve;` +
` rej = reject;` +
`});` +
`window.${promiseName}.resolve = res;` +
`window.${promiseName}.reject = rej;` +
`window.${promiseName};`;
const obj = await this.rpcClient.send('Runtime.evaluate', {
command: script,
appIdKey: this.appIdKey,
Expand Down Expand Up @@ -458,20 +458,31 @@ class RemoteDebugger extends events.EventEmitter {
if (!err.message.includes(`'Runtime.awaitPromise' was not found`)) {
throw err;
}

// awaitPromise is not always available, so simulate it with poll
const retryWait = 100;
const timeout = (args.length >= 3) ? args[2] : RPC_RESPONSE_TIMEOUT_MS;
// if the timeout math turns up 0 retries, make sure it happens once
const retries = parseInt(timeout / retryWait, 10) || 1;
const timer = new timing.Timer().start();
log.debug(`Waiting up to ${timeout}ms for async execute to finish`);
res = await retryInterval(retries, retryWait, async () => {
// the atom _will_ return, either because it finished or an error
// including a timeout error
if (await this.executeAtom('execute_script', [`return window.hasOwnProperty('${promiseName}Value');`, [null, null], subcommandTimeout], frames)) {
const hasValue = await this.rpcClient.send('Runtime.evaluate', {
command: `window.hasOwnProperty('${promiseName}Value');`,
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
returnByValue: false,
});
if (hasValue) {
// we only put the property on `window` when the callback is called,
// so if it is there, everything is done
return await this.executeAtom('execute_script', [`return window.${promiseName}Value;`, [null, null], subcommandTimeout], frames);
return await this.rpcClient.send('Runtime.evaluate', {
command: `window.${promiseName}Value;`,
appIdKey: this.appIdKey,
pageIdKey: this.pageIdKey,
returnByValue: false,
});
}
// throw a TimeoutError, or else it needs to be caught and re-thrown
throw new errors.TimeoutError(`Timed out waiting for asynchronous script ` +
Expand Down
2 changes: 1 addition & 1 deletion lib/rpc-message-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default class RpcMessageHandler extends EventEmitters {

if (msgId) {
if (this.listenerCount(msgId)) {
if (result?.result?.value) {
if (_.has(result?.result, 'value')) {
result = result.result.value;
}
this.emit(msgId, error, result);
Expand Down
10 changes: 10 additions & 0 deletions test/functional/html/frameset.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<title>Remote debugger test page: frames</title>
</head>
<frameset cols="*, *, *">
<frame name="first" src="subframe1.html"/>
<frame name="second" src="subframe2.html"/>
<frame name="third" src="subframe3.html" id="frame3" />
</frameset>
</html>
9 changes: 9 additions & 0 deletions test/functional/html/subframe1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<head>
<title>Remote debugger test page: Sub frame 1</title>
</head>
<body>
<h1>Sub frame 1</h1>
<a href="index.html" target="namedwindow">Open a named window</a>
</body>
</html>
8 changes: 8 additions & 0 deletions test/functional/html/subframe2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head>
<title>Remote debugger test page: Sub frame 2</title>
</head>
<body>
<h1>Sub frame 2</h1>
</body>
</html>
8 changes: 8 additions & 0 deletions test/functional/html/subframe3.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head>
<title>Remote debugger test page: Sub frame 3</title>
</head>
<body>
<h1>Sub frame 3</h1>
</body>
</html>
17 changes: 17 additions & 0 deletions test/functional/safari-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,23 @@ describe('Safari remote debugger', function () {
await rd.executeAtomAsync('execute_async_script', [script, [], timeout], [])
.should.eventually.be.rejectedWith(/Timed out waiting for/);
});

it('should be able to execute asynchronously in frame', async function () {
await connect(rd);
const page = _.find(await rd.selectApp(address), (page) => page.title === PAGE_TITLE);
const [appIdKey, pageIdKey] = page.id.split('.').map((id) => parseInt(id, 10));
await rd.selectPage(appIdKey, pageIdKey);

// go to the frameset page
await rd.navToUrl(`${address}/frameset.html`);

// get the correct frame
const {WINDOW: frame} = await rd.executeAtom('frame_by_id_or_name', ['first'], []);

const script = `arguments[arguments.length - 1](document.getElementsByTagName('h1')[0].innerHTML);`;
await rd.executeAtomAsync('execute_async_script', [script, [], timeout], [frame])
.should.eventually.eql('Sub frame 1');
});
});

it(`should be able to call 'selectApp' after already connecting to app`, async function () {
Expand Down