Path: blob/trunk/javascript/selenium-webdriver/test/lib/http_test.js
2885 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617'use strict'1819const assert = require('node:assert'),20sinon = require('sinon')2122const Capabilities = require('selenium-webdriver/lib/capabilities').Capabilities,23Command = require('selenium-webdriver/lib/command').Command,24CommandName = require('selenium-webdriver/lib/command').Name,25error = require('selenium-webdriver/lib/error'),26http = require('selenium-webdriver/lib/http'),27Session = require('selenium-webdriver/lib/session').Session,28WebElement = require('selenium-webdriver/lib/webdriver').WebElement2930describe('http', function () {31describe('buildPath', function () {32it('properly replaces path segments with command parameters', function () {33const parameters = { sessionId: 'foo', url: 'http://www.google.com' }34const finalPath = http.buildPath('/session/:sessionId/url', parameters)35assert.strictEqual(finalPath, '/session/foo/url')36assert.deepStrictEqual(parameters, { url: 'http://www.google.com' })37})3839it('handles web element references', function () {40const parameters = { sessionId: 'foo', id: WebElement.buildId('bar') }4142const finalPath = http.buildPath('/session/:sessionId/element/:id/click', parameters)43assert.strictEqual(finalPath, '/session/foo/element/bar/click')44assert.deepStrictEqual(parameters, {})45})4647it('throws if missing a parameter', function () {48assert.throws(49() => http.buildPath('/session/:sessionId', {}),50function (err) {51return err instanceof error.InvalidArgumentError && 'Missing required parameter: sessionId' === err.message52},53)5455assert.throws(56() =>57http.buildPath('/session/:sessionId/element/:id', {58sessionId: 'foo',59}),60function (err) {61return err instanceof error.InvalidArgumentError && 'Missing required parameter: id' === err.message62},63)64})6566it('does not match on segments that do not start with a colon', function () {67assert.strictEqual(http.buildPath('/session/foo:bar/baz', {}), '/session/foo:bar/baz')68})69})7071describe('Executor', function () {72let executor73let client74let send7576beforeEach(function setUp() {77client = new http.Client()78send = sinon.stub(client, 'send')79executor = new http.Executor(client)80})8182describe('command routing', function () {83it('rejects unrecognized commands', function () {84return executor.execute(new Command('fake-name')).then(assert.fail, (err) => {85if (err instanceof error.UnknownCommandError && 'Unrecognized command: fake-name' === err.message) {86return87}88throw err89})90})9192it('rejects promise if client fails to send request', function () {93let error = new Error('boom')94send.returns(Promise.reject(error))95return assertFailsToSend(new Command(CommandName.NEW_SESSION)).then(function (e) {96assert.strictEqual(error, e)97assertSent('POST', '/session', {}, [['Accept', 'application/json; charset=utf-8']])98})99})100101it('can execute commands with no URL parameters', function () {102const resp = JSON.stringify({ sessionId: 'abc123' })103send.returns(Promise.resolve(new http.Response(200, {}, resp)))104105let command = new Command(CommandName.NEW_SESSION)106return assertSendsSuccessfully(command).then(function (_response) {107assertSent('POST', '/session', {}, [['Accept', 'application/json; charset=utf-8']])108})109})110111it('rejects commands missing URL parameters', async function () {112let command = new Command(CommandName.FIND_CHILD_ELEMENT)113.setParameter('sessionId', 's123')114// Let this be missing: setParameter('id', {'ELEMENT': 'e456'}).115.setParameter('using', 'id')116.setParameter('value', 'foo')117118try {119await executor.execute(command)120return Promise.reject(Error('should have thrown'))121} catch (err) {122assert.strictEqual(err.constructor, error.InvalidArgumentError)123assert.strictEqual(err.message, 'Missing required parameter: id')124}125assert.ok(!send.called)126})127128it('replaces URL parameters with command parameters', function () {129const command = new Command(CommandName.GET)130.setParameter('sessionId', 's123')131.setParameter('url', 'http://www.google.com')132133send.returns(Promise.resolve(new http.Response(200, {}, '')))134135return assertSendsSuccessfully(command).then(function (_response) {136assertSent('POST', '/session/s123/url', { url: 'http://www.google.com' }, [137['Accept', 'application/json; charset=utf-8'],138])139})140})141142describe('uses correct URL', function () {143beforeEach(() => (executor = new http.Executor(client)))144145describe('in W3C mode', function () {146test(CommandName.MAXIMIZE_WINDOW, { sessionId: 's123' }, true, 'POST', '/session/s123/window/maximize')147148// This is consistent b/w legacy and W3C, just making sure.149test(150CommandName.GET,151{ sessionId: 's123', url: 'http://www.example.com' },152true,153'POST',154'/session/s123/url',155{ url: 'http://www.example.com' },156)157})158159function test(command, parameters, w3c, expectedMethod, expectedUrl, opt_expectedParams) {160it(`command=${command}`, function () {161const resp = JSON.stringify({ sessionId: 'abc123' })162send.returns(Promise.resolve(new http.Response(200, {}, resp)))163164let cmd = new Command(command).setParameters(parameters)165executor.w3c = w3c166return executor.execute(cmd).then(function () {167assertSent(expectedMethod, expectedUrl, opt_expectedParams || {}, [168['Accept', 'application/json; charset=utf-8'],169])170})171})172}173})174})175176describe('response parsing', function () {177it('extracts value from JSON response', function () {178const responseObj = {179status: error.ErrorCode.SUCCESS,180value: 'http://www.google.com',181}182183const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')184185send.returns(Promise.resolve(new http.Response(200, {}, JSON.stringify(responseObj))))186187return executor.execute(command).then(function (response) {188assertSent('GET', '/session/s123/url', {}, [['Accept', 'application/json; charset=utf-8']])189assert.strictEqual(response, 'http://www.google.com')190})191})192193describe('extracts Session from NEW_SESSION response', function () {194beforeEach(() => (executor = new http.Executor(client)))195196const command = new Command(CommandName.NEW_SESSION)197198describe('fails if server returns invalid response', function () {199describe('(empty response)', function () {200test(true)201test(false)202203function test(w3c) {204it('w3c === ' + w3c, function () {205send.returns(Promise.resolve(new http.Response(200, {}, '')))206executor.w3c = w3c207return executor.execute(command).then(208() => assert.fail('expected to fail'),209(e) => {210if (!e.message.startsWith('Unable to parse')) {211throw e212}213},214)215})216}217})218219describe('(no session ID)', function () {220test(true)221test(false)222223function test(w3c) {224it('w3c === ' + w3c, function () {225let resp = { value: { name: 'Bob' } }226send.returns(Promise.resolve(new http.Response(200, {}, JSON.stringify(resp))))227executor.w3c = w3c228return executor.execute(command).then(229() => assert.fail('expected to fail'),230(e) => {231if (!e.message.startsWith('Unable to parse')) {232throw e233}234},235)236})237}238})239})240241it('handles legacy response', function () {242const rawResponse = {243sessionId: 's123',244status: 0,245value: { name: 'Bob' },246}247248send.returns(Promise.resolve(new http.Response(200, {}, JSON.stringify(rawResponse))))249250assert.ok(!executor.w3c)251return executor.execute(command).then(function (response) {252assert.ok(response instanceof Session)253assert.strictEqual(response.getId(), 's123')254255let caps = response.getCapabilities()256assert.ok(caps instanceof Capabilities)257assert.strictEqual(caps.get('name'), 'Bob')258259assert.ok(!executor.w3c)260})261})262263it('auto-upgrades on W3C response', function () {264let rawResponse = {265value: {266sessionId: 's123',267value: {268name: 'Bob',269},270},271}272273send.returns(Promise.resolve(new http.Response(200, {}, JSON.stringify(rawResponse))))274275assert.ok(!executor.w3c)276return executor.execute(command).then(function (response) {277assert.ok(response instanceof Session)278assert.strictEqual(response.getId(), 's123')279280let caps = response.getCapabilities()281assert.ok(caps instanceof Capabilities)282assert.strictEqual(caps.get('name'), 'Bob')283284assert.ok(executor.w3c)285})286})287288it('if w3c, does not downgrade on legacy response', function () {289const rawResponse = { sessionId: 's123', status: 0, value: null }290291send.returns(Promise.resolve(new http.Response(200, {}, JSON.stringify(rawResponse))))292293executor.w3c = true294return executor.execute(command).then(function (response) {295assert.ok(response instanceof Session)296assert.strictEqual(response.getId(), 's123')297assert.strictEqual(response.getCapabilities().size, 0)298assert.ok(executor.w3c, 'should never downgrade')299})300})301302it('handles legacy new session failures', function () {303let rawResponse = {304status: error.ErrorCode.NO_SUCH_ELEMENT,305value: { message: 'hi' },306}307308send.returns(Promise.resolve(new http.Response(500, {}, JSON.stringify(rawResponse))))309310return executor.execute(command).then(311() => assert.fail('should have failed'),312(e) => {313assert.ok(e instanceof error.NoSuchElementError)314assert.strictEqual(e.message, 'hi')315},316)317})318319it('handles w3c new session failures', function () {320let rawResponse = {321value: { error: 'no such element', message: 'oops' },322}323324send.returns(Promise.resolve(new http.Response(500, {}, JSON.stringify(rawResponse))))325326return executor.execute(command).then(327() => assert.fail('should have failed'),328(e) => {329assert.ok(e instanceof error.NoSuchElementError)330assert.strictEqual(e.message, 'oops')331},332)333})334})335336it('handles JSON null', function () {337const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')338339send.returns(Promise.resolve(new http.Response(200, {}, 'null')))340341return executor.execute(command).then(function (response) {342assertSent('GET', '/session/s123/url', {}, [['Accept', 'application/json; charset=utf-8']])343assert.strictEqual(response, null)344})345})346347describe('falsy values', function () {348test(0)349test(false)350test('')351352function test(value) {353it(`value=${value}`, function () {354const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')355356send.returns(Promise.resolve(new http.Response(200, {}, JSON.stringify({ status: 0, value: value }))))357358return executor.execute(command).then(function (response) {359assertSent('GET', '/session/s123/url', {}, [['Accept', 'application/json; charset=utf-8']])360assert.strictEqual(response, value)361})362})363}364})365366it('handles non-object JSON', function () {367const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')368369send.returns(Promise.resolve(new http.Response(200, {}, '123')))370371return executor.execute(command).then(function (response) {372assertSent('GET', '/session/s123/url', {}, [['Accept', 'application/json; charset=utf-8']])373assert.strictEqual(response, 123)374})375})376377it('returns body text when 2xx but not JSON', function () {378const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')379380send.returns(Promise.resolve(new http.Response(200, {}, 'hello, world\r\ngoodbye, world!')))381382return executor.execute(command).then(function (response) {383assertSent('GET', '/session/s123/url', {}, [['Accept', 'application/json; charset=utf-8']])384assert.strictEqual(response, 'hello, world\ngoodbye, world!')385})386})387388it('returns body text when 2xx but invalid JSON', function () {389const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')390391send.returns(Promise.resolve(new http.Response(200, {}, '[')))392393return executor.execute(command).then(function (response) {394assertSent('GET', '/session/s123/url', {}, [['Accept', 'application/json; charset=utf-8']])395assert.strictEqual(response, '[')396})397})398399it('returns null if no body text and 2xx', function () {400const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')401402send.returns(Promise.resolve(new http.Response(200, {}, '')))403404return executor.execute(command).then(function (response) {405assertSent('GET', '/session/s123/url', {}, [['Accept', 'application/json; charset=utf-8']])406assert.strictEqual(response, null)407})408})409410it('returns normalized body text when 2xx but not JSON', function () {411const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')412413send.returns(Promise.resolve(new http.Response(200, {}, '\r\n\n\n\r\n')))414415return executor.execute(command).then(function (response) {416assertSent('GET', '/session/s123/url', {}, [['Accept', 'application/json; charset=utf-8']])417assert.strictEqual(response, '\n\n\n\n')418})419})420421it('throws UnsupportedOperationError for 404 and body not JSON', function () {422const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')423424send.returns(Promise.resolve(new http.Response(404, {}, 'hello, world\r\ngoodbye, world!')))425426return executor427.execute(command)428.then(429() => assert.fail('should have failed'),430checkError(error.UnsupportedOperationError, 'getCurrentUrl: hello, world\ngoodbye, world!'),431)432})433434it('throws WebDriverError for generic 4xx when body not JSON', function () {435const command = new Command(CommandName.GET_CURRENT_URL).setParameter('sessionId', 's123')436437send.returns(Promise.resolve(new http.Response(500, {}, 'hello, world\r\ngoodbye, world!')))438439return executor440.execute(command)441.then(442() => assert.fail('should have failed'),443checkError(error.WebDriverError, 'hello, world\ngoodbye, world!'),444)445.then(function () {446assertSent('GET', '/session/s123/url', {}, [['Accept', 'application/json; charset=utf-8']])447})448})449})450451it('canDefineNewCommands', function () {452executor.defineCommand('greet', 'GET', '/person/:name')453454const command = new Command('greet').setParameter('name', 'Bob')455456send.returns(Promise.resolve(new http.Response(200, {}, '')))457458return assertSendsSuccessfully(command).then(function (_response) {459assertSent('GET', '/person/Bob', {}, [['Accept', 'application/json; charset=utf-8']])460})461})462463it('canRedefineStandardCommands', function () {464executor.defineCommand(CommandName.GO_BACK, 'POST', '/custom/back')465466const command = new Command(CommandName.GO_BACK).setParameter('times', 3)467468send.returns(Promise.resolve(new http.Response(200, {}, '')))469470return assertSendsSuccessfully(command).then(function (_response) {471assertSent('POST', '/custom/back', { times: 3 }, [['Accept', 'application/json; charset=utf-8']])472})473})474475it('accepts promised http clients', function () {476executor = new http.Executor(Promise.resolve(client))477478const resp = JSON.stringify({ sessionId: 'abc123' })479send.returns(Promise.resolve(new http.Response(200, {}, resp)))480481let command = new Command(CommandName.NEW_SESSION)482return executor.execute(command).then((_response) => {483assertSent('POST', '/session', {}, [['Accept', 'application/json; charset=utf-8']])484})485})486487function entries(map) {488let entries = []489for (let e of map.entries()) {490entries.push(e)491}492return entries493}494495function checkError(type, message) {496return function (e) {497if (e instanceof type) {498assert.strictEqual(e.message, message)499} else {500throw e501}502}503}504505function assertSent(method, path, data, headers) {506assert.ok(507send.calledWith(508sinon.match(function (value) {509assert.strictEqual(value.method, method)510assert.strictEqual(value.path, path)511assert.deepStrictEqual(value.data, data)512assert.deepStrictEqual(entries(value.headers), headers)513return true514}),515),516)517}518519function assertSendsSuccessfully(command) {520return executor.execute(command).then(function (response) {521return response522})523}524525function assertFailsToSend(command, _opt_onError) {526return executor.execute(command).then(527() => {528throw Error('should have failed')529},530(e) => {531return e532},533)534}535})536})537538539