Skip to content

Commit 182b466

Browse files
committed
Properly handle responses in WebDriver.attachToSession
Fixes #2017
1 parent 38c583e commit 182b466

File tree

6 files changed

+215
-37
lines changed

6 files changed

+215
-37
lines changed

javascript/node/selenium-webdriver/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
Sorry for the churn!
88
* FIXED: capabilities serialization now properly handles undefined vs.
99
false-like values.
10+
* FIXED: properly handle responses from the remote end in
11+
`WebDriver.attachToSession`
1012

1113
## v2.53.1
1214

javascript/node/selenium-webdriver/http/index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,15 +471,19 @@ class Executor {
471471
let parsed =
472472
parseHttpResponse(/** @type {!HttpResponse} */ (response), this.w3c);
473473

474-
if (command.getName() === cmd.Name.NEW_SESSION) {
474+
if (command.getName() === cmd.Name.NEW_SESSION
475+
|| command.getName() === cmd.Name.DESCRIBE_SESSION) {
475476
if (!parsed || !parsed['sessionId']) {
476477
throw new error.WebDriverError(
477478
'Unable to parse new session response: ' + response.body);
478479
}
479480

480481
// The remote end is a W3C compliant server if there is no `status`
481-
// field in the response.
482-
this.w3c = this.w3c || !('status' in parsed);
482+
// field in the response. This is not appliable for the DESCRIBE_SESSION
483+
// command, which is not defined in the W3C spec.
484+
if (command.getName() === cmd.Name.NEW_SESSION) {
485+
this.w3c = this.w3c || !('status' in parsed);
486+
}
483487

484488
return new Session(parsed['sessionId'], parsed['value']);
485489
}

javascript/node/selenium-webdriver/lib/webdriver.js

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -87,30 +87,6 @@ class WebElementCondition extends Condition {
8787
//////////////////////////////////////////////////////////////////////////////
8888

8989

90-
/**
91-
* Sends a command to the server that is expected to return the details for a
92-
* {@link Session}. This may either be an existing session, or a newly created
93-
* one.
94-
*
95-
* @param {!command.Executor} executor Command executor to use when
96-
* querying for session details.
97-
* @param {!command.Command} command The command to send to fetch the session
98-
* details.
99-
* @param {string} description A descriptive debug label for this action.
100-
* @param {promise.ControlFlow=} opt_flow The control flow all driver
101-
* commands should execute under. Defaults to the
102-
* {@link promise.controlFlow() currently active} control flow.
103-
* @return {!WebDriver} A new WebDriver client for the session.
104-
*/
105-
function acquireSession(executor, command, description, opt_flow) {
106-
let flow = opt_flow || promise.controlFlow();
107-
let session = flow.execute(function() {
108-
return executeCommand(executor, command);
109-
}, description);
110-
return new WebDriver(session, executor, flow);
111-
}
112-
113-
11490
/**
11591
* Translates a command to its wire-protocol representation before passing it
11692
* to the given `executor` for execution.
@@ -314,11 +290,24 @@ class WebDriver {
314290
* @return {!WebDriver} A new client for the specified session.
315291
*/
316292
static attachToSession(executor, sessionId, opt_flow) {
317-
return acquireSession(executor,
318-
new command.Command(command.Name.DESCRIBE_SESSION).
319-
setParameter('sessionId', sessionId),
320-
'WebDriver.attachToSession()',
321-
opt_flow);
293+
let flow = opt_flow || promise.controlFlow();
294+
let cmd = new command.Command(command.Name.DESCRIBE_SESSION)
295+
.setParameter('sessionId', sessionId);
296+
let session = flow.execute(
297+
() => executeCommand(executor, cmd),
298+
'WebDriver.attachToSession()');
299+
300+
session = session.catch(err => {
301+
// The DESCRIBE_SESSION command is not supported by the W3C spec, so if
302+
// we get back an unknown command, just return a session with unknown
303+
// capabilities.
304+
if (err instanceof error.UnknownCommandError) {
305+
return new Session(sessionId, new Capabilities);
306+
}
307+
throw err;
308+
});
309+
310+
return new WebDriver(session, executor, flow);
322311
}
323312

324313
/**
@@ -334,11 +323,13 @@ class WebDriver {
334323
* @return {!WebDriver} The driver for the newly created session.
335324
*/
336325
static createSession(executor, desiredCapabilities, opt_flow) {
337-
return acquireSession(executor,
338-
new command.Command(command.Name.NEW_SESSION).
339-
setParameter('desiredCapabilities', desiredCapabilities),
340-
'WebDriver.createSession()',
341-
opt_flow);
326+
let flow = opt_flow || promise.controlFlow();
327+
let cmd = new command.Command(command.Name.NEW_SESSION)
328+
.setParameter('desiredCapabilities', desiredCapabilities) ;
329+
let session = flow.execute(
330+
() => executeCommand(executor, cmd),
331+
'WebDriver.createSession()');
332+
return new WebDriver(session, executor, flow);
342333
}
343334

344335
/**
@@ -418,6 +409,13 @@ class WebDriver {
418409
this.fileDetector_ = detector;
419410
}
420411

412+
/**
413+
* @return {!command.Executor} The command executor used by this instance.
414+
*/
415+
getExecutor() {
416+
return this.executor_;
417+
}
418+
421419
/**
422420
* @return {!promise.Promise<!Session>} A promise for this client's
423421
* session.

javascript/node/selenium-webdriver/test/http/http_test.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,111 @@ describe('Executor', function() {
328328
});
329329
});
330330

331+
describe('extracts Session from DESCRIBE_SESSION response', function() {
332+
let command;
333+
334+
beforeEach(function() {
335+
executor = new Executor(client);
336+
command = new Command(CommandName.DESCRIBE_SESSION)
337+
.setParameter('sessionId', 'foo');
338+
});
339+
340+
describe('fails if server returns invalid response', function() {
341+
describe('(empty response)', function() {
342+
test(true);
343+
test(false);
344+
345+
function test(w3c) {
346+
it('w3c === ' + w3c, function() {
347+
send.returns(Promise.resolve(new HttpResponse(200, {}, '')));
348+
executor.w3c = w3c;
349+
return executor.execute(command).then(
350+
() => assert.fail('expected to fail'),
351+
(e) => {
352+
if (!e.message.startsWith('Unable to parse')) {
353+
throw e;
354+
}
355+
});
356+
});
357+
}
358+
});
359+
360+
describe('(no session ID)', function() {
361+
test(true);
362+
test(false);
363+
364+
function test(w3c) {
365+
it('w3c === ' + w3c, function() {
366+
let resp = {value:{name: 'Bob'}};
367+
send.returns(Promise.resolve(
368+
new HttpResponse(200, {}, JSON.stringify(resp))));
369+
executor.w3c = w3c;
370+
return executor.execute(command).then(
371+
() => assert.fail('expected to fail'),
372+
(e) => {
373+
if (!e.message.startsWith('Unable to parse')) {
374+
throw e;
375+
}
376+
});
377+
});
378+
}
379+
});
380+
});
381+
382+
it('handles legacy response', function() {
383+
var rawResponse = {sessionId: 's123', status: 0, value: {name: 'Bob'}};
384+
385+
send.returns(Promise.resolve(
386+
new HttpResponse(200, {}, JSON.stringify(rawResponse))));
387+
388+
assert.ok(!executor.w3c);
389+
return executor.execute(command).then(function(response) {
390+
assert.ok(response instanceof Session);
391+
assert.equal(response.getId(), 's123');
392+
393+
let caps = response.getCapabilities();
394+
assert.ok(caps instanceof Capabilities);
395+
assert.equal(caps.get('name'), 'Bob');
396+
397+
assert.ok(!executor.w3c);
398+
});
399+
});
400+
401+
it('does not auto-upgrade on W3C response', function() {
402+
var rawResponse = {sessionId: 's123', value: {name: 'Bob'}};
403+
404+
send.returns(Promise.resolve(
405+
new HttpResponse(200, {}, JSON.stringify(rawResponse))));
406+
407+
assert.ok(!executor.w3c);
408+
return executor.execute(command).then(function(response) {
409+
assert.ok(response instanceof Session);
410+
assert.equal(response.getId(), 's123');
411+
412+
let caps = response.getCapabilities();
413+
assert.ok(caps instanceof Capabilities);
414+
assert.equal(caps.get('name'), 'Bob');
415+
416+
assert.ok(!executor.w3c);
417+
});
418+
});
419+
420+
it('if w3c, does not downgrade on legacy response', function() {
421+
var rawResponse = {sessionId: 's123', status: 0, value: null};
422+
423+
send.returns(Promise.resolve(
424+
new HttpResponse(200, {}, JSON.stringify(rawResponse))));
425+
426+
executor.w3c = true;
427+
return executor.execute(command).then(function(response) {
428+
assert.ok(response instanceof Session);
429+
assert.equal(response.getId(), 's123');
430+
assert.equal(response.getCapabilities().size, 0);
431+
assert.ok(executor.w3c, 'should never downgrade');
432+
});
433+
});
434+
});
435+
331436
it('handles JSON null', function() {
332437
var command = new Command(CommandName.GET_CURRENT_URL)
333438
.setParameter('sessionId', 's123');

javascript/node/selenium-webdriver/test/lib/webdriver_test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,22 @@ describe('WebDriver', function() {
272272
(actual) => assert.strictEqual(actual, e));
273273
});
274274

275+
it('remote end does not recognize DESCRIBE_SESSION command', function() {
276+
let e = new error.UnknownCommandError;
277+
let executor = new FakeExecutor().
278+
expect(CName.DESCRIBE_SESSION).
279+
withParameters({'sessionId': SESSION_ID}).
280+
andReturnError(e).
281+
end();
282+
283+
let driver = WebDriver.attachToSession(executor, SESSION_ID);
284+
return driver.getSession().then(session => {
285+
assert.ok(session instanceof Session);
286+
assert.strictEqual(session.getId(), SESSION_ID);
287+
assert.equal(session.getCapabilities().size, 0);
288+
});
289+
});
290+
275291
it('usesActiveFlowByDefault', function() {
276292
let executor = new FakeExecutor().
277293
expect(CName.DESCRIBE_SESSION).
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
'use strict';
19+
20+
var WebDriver = require('..').WebDriver,
21+
assert = require('../testing/assert'),
22+
test = require('../lib/test'),
23+
Pages = test.Pages;
24+
25+
26+
test.suite(function(env) {
27+
var browsers = env.browsers;
28+
29+
var driver;
30+
test.before(function() {
31+
driver = env.builder().build();
32+
});
33+
34+
test.after(function() {
35+
driver.quit();
36+
});
37+
38+
test.it('can connect to an existing session', function() {
39+
driver.get(Pages.simpleTestPage);
40+
assert(driver.getTitle()).equalTo('Hello WebDriver');
41+
42+
return driver.getSession().then(session1 => {
43+
let driver2 = WebDriver.attachToSession(
44+
driver.getExecutor(),
45+
session1.getId());
46+
47+
assert(driver2.getTitle()).equalTo('Hello WebDriver');
48+
49+
let session2Id = driver2.getSession().then(s => s.getId());
50+
assert(session2Id).equalTo(session1.getId());
51+
});
52+
});
53+
});

0 commit comments

Comments
 (0)