selenium-webdriver 4.4.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGES.md CHANGED
@@ -1,3 +1,60 @@
1
+ ## v4.6.0
2
+
3
+ #### :rocket: New Feature
4
+
5
+ * [grid] Add ability to use Enter key to confirm (#11178)
6
+ * [grid][ui] Add search field for running sessions (#11197)
7
+ * Add support for selenium manager (#11189)
8
+
9
+ #### :bug: Bug Fix
10
+
11
+ * isPromise() thenable fix (#11048)
12
+ * Fix: making methods static (#11182)
13
+
14
+ #### :nail_care: Polish
15
+
16
+ * fix a tiny typo in chromium.js (#11073)
17
+ * Adding CDP files for v107 and removing v104
18
+
19
+ ## v4.5.0
20
+
21
+ #### :rocket: New Feature
22
+
23
+ * Adds 'Select' support package
24
+ * selectByIndex
25
+ * selectByValue
26
+ * selectByVisibleText
27
+ * getAllSelectedOptions
28
+ * getFirstSelectedOption
29
+ * deselectAll
30
+ * deselectByVisibleText
31
+ * deselectByIndex
32
+ * deselectByValue
33
+ * Add support for Actions API sendKeys to designated element
34
+ * Adds mouse button enum for forward and backward navigation
35
+
36
+ #### :bug: Bug Fix
37
+
38
+ * Set min node support to 14.x.x (issue 1 in #10970)
39
+ * fix: geckodriver session with node 18.x.x (issue 2 in #10970)
40
+ * fix: JS firefox driver crashes on setting a profile (fixed with [commit](https://github.com/SeleniumHQ/selenium/commit/fa6deeea6bda1e73317157845772e114bd569b7d))
41
+ * fix: "SetExperimental" option is not available in webdriverjs (Javascript/Typescript) (#10959)
42
+ * fix: Do not allow Select class to select disabled options (#10812)
43
+
44
+ #### :nail_care: Polish
45
+
46
+ * Stop sending desiredCapabilities to local end and remote end
47
+ * tests: Quit driver instance after each test
48
+ * tests: Adds select by multiple(index,value, text) tests
49
+ * chore/code style changes, add util (#10975)
50
+ * chore/code style changes to common format (#10964)
51
+ * destructuring exports
52
+ * Moving from static to const to make js-dossier happy
53
+ * Removing circular dependency Between webdriver.js and http.js
54
+ * fix some typos in code and documentation
55
+ * add cdp v105 remove v102
56
+ * add cdp v106 remove v103
57
+
1
58
  ## v4.4.0
2
59
  * Add support CDP 104 and remove CDP 101
3
60
 
@@ -1129,3 +1186,4 @@ major version release (i.e. 1.0.0).
1129
1186
  * Initial release for npm:
1130
1187
 
1131
1188
  npm install selenium-webdriver
1189
+
package/README.md CHANGED
@@ -13,19 +13,19 @@ Selenium may be installed via npm with
13
13
  You will need to download additional components to work with each of the major
14
14
  browsers. The drivers for Chrome, Firefox, and Microsoft's IE and Edge web
15
15
  browsers are all standalone executables that should be placed on your system
16
- [PATH]. Apple's safaridriver is shipped with Safari 10 for OS X El Capitan and
17
- macOS Sierra. You will need to enable Remote Automation in the Develop menu of
18
- Safari 10 before testing.
16
+ [PATH]. Apple's safaridriver (v10 and above) can be found at the
17
+ following path – /usr/bin/safaridriver. To enable automation on safari,
18
+ you need to run command `safaridriver --enable`.
19
19
 
20
20
 
21
- | Browser | Component |
22
- | ----------------- | ---------------------------------- |
23
- | Chrome | [chromedriver(.exe)][chrome] |
24
- | Internet Explorer | [IEDriverServer.exe][release] |
25
- | Edge | [MicrosoftWebDriver.msi][edge] |
26
- | Firefox | [geckodriver(.exe)][geckodriver] |
27
- | Opera | [operadriver(.exe)][operadriver] |
28
- | Safari | [safaridriver] |
21
+ | Browser | Component |
22
+ |:-------------------|:------------------------------------|
23
+ | Chrome | [chromedriver(.exe)][chrome] |
24
+ | Internet Explorer | [IEDriverServer.exe][release] |
25
+ | Edge | [MicrosoftWebDriver.msi][edge] |
26
+ | Firefox | [geckodriver(.exe)][geckodriver] |
27
+ | Opera | [operadriver(.exe)][operadriver] |
28
+ | Safari | [safaridriver] |
29
29
 
30
30
  ## Usage
31
31
 
@@ -38,7 +38,7 @@ const {Builder, Browser, By, Key, until} = require('selenium-webdriver');
38
38
  (async function example() {
39
39
  let driver = await new Builder().forBrowser(Browser.FIREFOX).build();
40
40
  try {
41
- await driver.get('http://www.google.com/ncr');
41
+ await driver.get('https://www.google.com/ncr');
42
42
  await driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN);
43
43
  await driver.wait(until.titleIs('webdriver - Google Search'), 1000);
44
44
  } finally {
@@ -92,7 +92,7 @@ To use the Selenium Server, you will need to install the
92
92
  download the latest server from [Selenium][release]. Once downloaded, run the
93
93
  server with
94
94
 
95
- java -jar selenium-server-standalone-2.45.0.jar
95
+ java -jar selenium-server-4.4.0.jar standalone
96
96
 
97
97
  You may configure your tests to run against a remote server through the Builder
98
98
  API:
@@ -135,17 +135,17 @@ will also have "best effort" support. Releases older than the latest LTS,
135
135
  _semver-major_ releases, and all unstable release branches (e.g. "v.Next")
136
136
  are considered strictly unsupported.
137
137
 
138
- For example, suppose the current LTS and stable releases are v6.9.5 and v7.5.0,
138
+ For example, suppose the current LTS and stable releases are v14.20.0 and v18.8.0,
139
139
  respectively. Then a Selenium release would have the following support levels:
140
140
 
141
- | Version | Support |
142
- | ------- | ------------- |
143
- | <= 6.8 | _unsupported_ |
144
- | 6.9 | supported |
145
- | 7.0-4 | best effort |
146
- | 7.5 | supported |
147
- | >= 7.5 | best effort |
148
- | v.Next | _unsupported_ |
141
+ | Version | Support |
142
+ |:----------:|:---------------:|
143
+ | <= 14.19 | _unsupported_ |
144
+ | 14.20.0 | supported |
145
+ | 18.0-7 | best effort |
146
+ | 18.8.0 | supported |
147
+ | >= 18.8.0 | best effort |
148
+ | v.Next | _unsupported_ |
149
149
 
150
150
  ### Support Level Definitions
151
151
 
@@ -165,13 +165,13 @@ respectively. Then a Selenium release would have the following support levels:
165
165
  If Node releases a new [LTS] each October and a new major version every 6
166
166
  months, the support window for selenium-webdriver will be roughly:
167
167
 
168
- | Date | LTS | Stable |
169
- | --------- | ---: | -----: |
170
- | (current) | 8.9 | 9.0 |
171
- | 2018-04 | 8.x | 10.0 |
172
- | 2018-10 | 10.x | 11.0 |
173
- | 2019-04 | 10.x | 12.0 |
174
- | 2019-10 | 12.x | 13.0 |
168
+ | Release | Status | END-OF-LIFE |
169
+ |:---------:|:----------------:|:------------:|
170
+ | v14.x | Maintenance LTS | 2023-04-30 |
171
+ | v16.x | Active LTS | 2023-09-11 |
172
+ | v18.x | Current | 2025-04-30 |
173
+ | v19.x | Pending | 2023-06-01 |
174
+ | v20 | Pending | 2026-04-30 |
175
175
 
176
176
  ## Issues
177
177
 
Binary file
Binary file
Binary file
package/chrome.js CHANGED
@@ -130,6 +130,7 @@
130
130
  const io = require('./io')
131
131
  const { Browser } = require('./lib/capabilities')
132
132
  const chromium = require('./chromium')
133
+ const { driverLocation } = require('./common/seleniumManager')
133
134
 
134
135
  /**
135
136
  * Name of the ChromeDriver executable.
@@ -150,20 +151,35 @@ class ServiceBuilder extends chromium.ServiceBuilder {
150
151
  /**
151
152
  * @param {string=} opt_exe Path to the server executable to use. If omitted,
152
153
  * the builder will attempt to locate the chromedriver on the current
153
- * PATH.
154
+ * PATH. If the chromedriver is not available in path, selenium-manager will
155
+ * download the chromedriver
154
156
  * @throws {Error} If provided executable does not exist, or the chromedriver
155
157
  * cannot be found on the PATH.
156
158
  */
157
159
  constructor(opt_exe) {
158
160
  let exe = opt_exe || locateSynchronously()
161
+
162
+ if (!exe) {
163
+ console.log(
164
+ ` The ChromeDriver could not be found on the current PATH, trying Selenium Manager`
165
+ )
166
+
167
+ try {
168
+ exe = driverLocation(Browser.CHROME)
169
+ } catch (err) {
170
+ console.log(`Unable to obtain driver using Selenium Manager: ${err}`)
171
+ }
172
+ }
173
+
159
174
  if (!exe) {
160
175
  throw Error(
161
- `The ChromeDriver could not be found on the current PATH. Please ` +
162
- `download the latest version of the ChromeDriver from ` +
163
- `http://chromedriver.storage.googleapis.com/index.html and ensure ` +
164
- `it can be found on your PATH.`
176
+ `The ChromeDriver could not be found on the current PATH.
177
+ Please download the latest version of the ChromeDriver
178
+ from http://chromedriver.storage.googleapis.com/index.html
179
+ and ensure it can be found on your PATH.`
165
180
  )
166
181
  }
182
+
167
183
  super(exe)
168
184
  }
169
185
  }
package/chromium.js CHANGED
@@ -706,7 +706,7 @@ class Driver extends webdriver.WebDriver {
706
706
  /**
707
707
  * Schedules a command to get Chromium network emulation settings.
708
708
  * @return {!Promise} A promise that will be resolved when network
709
- * emulation settings are retrievied.
709
+ * emulation settings are retrieved.
710
710
  */
711
711
  getNetworkConditions() {
712
712
  return this.execute(new command.Command(Command.GET_NETWORK_CONDITIONS))
@@ -0,0 +1,91 @@
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
+ /**
19
+ * This implementation is still in beta, and may change.
20
+ *
21
+ * Wrapper for getting information from the Selenium Manager binaries
22
+ */
23
+
24
+ const { platform } = require('process')
25
+ const path = require('path')
26
+ const fs = require('fs')
27
+ const execSync = require('child_process').execSync
28
+
29
+ /**
30
+ * currently supported browsers for selenium-manager
31
+ * @type {string[]}
32
+ */
33
+ const Browser = ['chrome', 'firefox', 'edge']
34
+
35
+ /**
36
+ * Determines the path of the correct Selenium Manager binary
37
+ * @returns {string}
38
+ */
39
+ function getBinary() {
40
+ const directory = {
41
+ darwin: 'macos',
42
+ win32: 'windows',
43
+ cygwin: 'windows',
44
+ linux: 'linux',
45
+ }[platform]
46
+
47
+ const file =
48
+ directory === 'windows' ? 'selenium-manager.exe' : 'selenium-manager'
49
+
50
+ const filePath = path.join(__dirname, '..', '/bin', directory, file)
51
+
52
+ if (!fs.existsSync(filePath)) {
53
+ throw new Error(`Unable to obtain Selenium Manager`)
54
+ }
55
+
56
+ return filePath
57
+ }
58
+
59
+ /**
60
+ * Determines the path of the correct driver
61
+ * @param {Browser|string} browser name to fetch the driver
62
+ * @returns {string} path of the driver location
63
+ */
64
+
65
+ function driverLocation(browser) {
66
+ if (!Browser.includes(browser.toLocaleString())) {
67
+ throw new Error(
68
+ `Unable to locate driver associated with browser name: ${browser}`
69
+ )
70
+ }
71
+
72
+ let args = [getBinary(), '--browser', browser]
73
+ let result
74
+
75
+ try {
76
+ result = execSync(args.join(' ')).toString()
77
+ } catch (e) {
78
+ throw new Error(
79
+ `Error executing command with ${args} : Error ${e.stderr.toString()}`
80
+ )
81
+ }
82
+
83
+ if (!result.startsWith('INFO\t')) {
84
+ throw new Error(`Unsuccessful command executed ${args}}`)
85
+ }
86
+
87
+ return result.replace('INFO\t', '').trim()
88
+ }
89
+
90
+ // PUBLIC API
91
+ module.exports = { driverLocation }
package/edge.js CHANGED
@@ -80,6 +80,7 @@
80
80
  const { Browser } = require('./lib/capabilities')
81
81
  const io = require('./io')
82
82
  const chromium = require('./chromium')
83
+ const { driverLocation } = require('./common/seleniumManager')
83
84
 
84
85
  /**
85
86
  * Name of the EdgeDriver executable.
@@ -106,6 +107,19 @@ class ServiceBuilder extends chromium.ServiceBuilder {
106
107
  */
107
108
  constructor(opt_exe) {
108
109
  let exe = opt_exe || locateSynchronously()
110
+
111
+ if (!exe) {
112
+ console.log(
113
+ `The WebDriver for Edge could not be found on the current PATH, trying Selenium Manager`
114
+ )
115
+
116
+ try {
117
+ exe = driverLocation('edge')
118
+ } catch (err) {
119
+ console.log(`Unable to obtain driver using Selenium Manager: ${err}`)
120
+ }
121
+ }
122
+
109
123
  if (!exe) {
110
124
  throw Error(
111
125
  `The WebDriver for Edge could not be found on the current PATH. Please download the ` +
@@ -114,6 +128,7 @@ class ServiceBuilder extends chromium.ServiceBuilder {
114
128
  `and ensure it can be found on your PATH.`
115
129
  )
116
130
  }
131
+
117
132
  super(exe)
118
133
  this.setLoopback(true)
119
134
  }
package/firefox.js CHANGED
@@ -127,6 +127,7 @@ const webdriver = require('./lib/webdriver')
127
127
  const zip = require('./io/zip')
128
128
  const { Browser, Capabilities } = require('./lib/capabilities')
129
129
  const { Zip } = require('./io/zip')
130
+ const { driverLocation } = require('./common/seleniumManager')
130
131
 
131
132
  /**
132
133
  * Thrown when there an add-on is malformed.
@@ -459,16 +460,27 @@ function locateSynchronously() {
459
460
  */
460
461
  function findGeckoDriver() {
461
462
  let exe = locateSynchronously()
463
+
464
+ if (!exe) {
465
+ console.log(
466
+ `The ${GECKO_DRIVER_EXE} executable could not be found on the current PATH, trying Selenium Manager`
467
+ )
468
+
469
+ try {
470
+ exe = driverLocation(Browser.FIREFOX)
471
+ } catch (err) {
472
+ console.log(`Unable to obtain driver using Selenium Manager: ${err}`)
473
+ }
474
+ }
475
+
462
476
  if (!exe) {
463
477
  throw Error(
464
- 'The ' +
465
- GECKO_DRIVER_EXE +
466
- ' executable could not be found on the current ' +
467
- 'PATH. Please download the latest version from ' +
468
- 'https://github.com/mozilla/geckodriver/releases/ ' +
469
- 'and ensure it can be found on your PATH.'
478
+ `The ${GECKO_DRIVER_EXE} executable could not be found on the current PATH.
479
+ Please download the latest version from https://github.com/mozilla/geckodriver/releases/
480
+ and ensure it can be found on your PATH.`
470
481
  )
471
482
  }
483
+
472
484
  return exe
473
485
  }
474
486
 
package/index.js CHANGED
@@ -38,6 +38,7 @@ const safari = require('./safari')
38
38
  const session = require('./lib/session')
39
39
  const until = require('./lib/until')
40
40
  const webdriver = require('./lib/webdriver')
41
+ const select = require('./lib/select')
41
42
 
42
43
  const Browser = capabilities.Browser
43
44
  const Capabilities = capabilities.Capabilities
@@ -793,3 +794,4 @@ exports.error = error
793
794
  exports.logging = logging
794
795
  exports.promise = promise
795
796
  exports.until = until
797
+ exports.Select = select.Select
package/io/exec.js CHANGED
@@ -121,7 +121,7 @@ class Command {
121
121
  * @param {Options=} opt_options The command options.
122
122
  * @return {!Command} The launched command.
123
123
  */
124
- module.exports = function exec(command, opt_options) {
124
+ function exec(command, opt_options) {
125
125
  const options = opt_options || {}
126
126
 
127
127
  let proc = childProcess.spawn(command, options.args || [], {
@@ -158,6 +158,9 @@ module.exports = function exec(command, opt_options) {
158
158
 
159
159
  // Exported to improve generated API documentation.
160
160
 
161
- module.exports.Command = Command
162
- module.exports.Options = Options
163
- module.exports.Result = Result
161
+ module.exports = {
162
+ Command,
163
+ Options,
164
+ Result,
165
+ exec,
166
+ }
package/lib/by.js CHANGED
@@ -41,9 +41,8 @@
41
41
  * {name: string}|
42
42
  * {partialLinkText: string}|
43
43
  * {tagName: string}|
44
- * {xpath: string})}
44
+ * {xpath: string})} ByHash
45
45
  */
46
- var ByHash // eslint-disable-line
47
46
 
48
47
  /**
49
48
  * Error thrown if an invalid character is encountered while escaping a CSS
package/lib/command.js CHANGED
@@ -204,7 +204,7 @@ class Executor {
204
204
  // PUBLIC API
205
205
 
206
206
  module.exports = {
207
- Command: Command,
208
- Name: Name,
209
- Executor: Executor,
207
+ Command,
208
+ Name,
209
+ Executor,
210
210
  }
package/lib/error.js CHANGED
@@ -17,6 +17,8 @@
17
17
 
18
18
  'use strict'
19
19
 
20
+ const { isObject } = require('./util')
21
+
20
22
  /**
21
23
  * The base WebDriver error type. This error type is only used directly when a
22
24
  * more appropriate category is not defined for the offending error.
@@ -505,7 +507,7 @@ function encodeError(err) {
505
507
  * @see https://w3c.github.io/webdriver/webdriver-spec.html#protocol
506
508
  */
507
509
  function isErrorResponse(data) {
508
- return data && typeof data === 'object' && typeof data.error === 'string'
510
+ return isObject(data) && typeof data.error === 'string'
509
511
  }
510
512
 
511
513
  /**
@@ -540,15 +542,13 @@ function throwDecodedError(data) {
540
542
  function checkLegacyResponse(responseObj) {
541
543
  // Handle the legacy Selenium error response format.
542
544
  if (
543
- responseObj &&
544
- typeof responseObj === 'object' &&
545
- typeof responseObj['status'] === 'number' &&
546
- responseObj['status'] !== 0
545
+ isObject(responseObj) &&
546
+ typeof responseObj.status === 'number' &&
547
+ responseObj.status !== 0
547
548
  ) {
548
- let status = responseObj['status']
549
- let ctor = LEGACY_ERROR_CODE_TO_TYPE.get(status) || WebDriverError
549
+ const { status, value } = responseObj
550
550
 
551
- let value = responseObj['value']
551
+ let ctor = LEGACY_ERROR_CODE_TO_TYPE.get(status) || WebDriverError
552
552
 
553
553
  if (!value || typeof value !== 'object') {
554
554
  throw new ctor(value + '')
package/lib/http.js CHANGED
@@ -31,7 +31,8 @@ const error = require('./error')
31
31
  const logging = require('./logging')
32
32
  const promise = require('./promise')
33
33
  const { Session } = require('./session')
34
- const { WebElement } = require('./webdriver')
34
+ const webElement = require('./webelement')
35
+ const { isObject } = require('./util')
35
36
 
36
37
  const getAttribute = requireAtom(
37
38
  'get-attribute.js',
@@ -462,7 +463,7 @@ const CLIENTS =
462
463
  class Executor {
463
464
  /**
464
465
  * @param {!(Client|IThenable<!Client>)} client The client to use for sending
465
- * requests to the server, or a promise-like object that will resolve to
466
+ * requests to the server, or a promise-like object that will resolve
466
467
  * to the client.
467
468
  */
468
469
  constructor(client) {
@@ -572,10 +573,7 @@ function parseHttpResponse(command, httpResponse) {
572
573
 
573
574
  if (parsed && typeof parsed === 'object') {
574
575
  let value = parsed.value
575
- let isW3C =
576
- value !== null &&
577
- typeof value === 'object' &&
578
- typeof parsed.status === 'undefined'
576
+ let isW3C = isObject(value) && typeof parsed.status === 'undefined'
579
577
 
580
578
  if (!isW3C) {
581
579
  error.checkLegacyResponse(parsed)
@@ -625,10 +623,10 @@ function buildPath(path, parameters) {
625
623
  let key = pathParameters[i].substring(2) // Trim the /:
626
624
  if (key in parameters) {
627
625
  let value = parameters[key]
628
- if (WebElement.isId(value)) {
626
+ if (webElement.isId(value)) {
629
627
  // When inserting a WebElement into the URL, only use its ID value,
630
628
  // not the full JSON.
631
- value = WebElement.extractId(value)
629
+ value = webElement.extractId(value)
632
630
  }
633
631
  path = path.replace(pathParameters[i], '/' + value)
634
632
  delete parameters[key]
@@ -645,10 +643,10 @@ function buildPath(path, parameters) {
645
643
  // PUBLIC API
646
644
 
647
645
  module.exports = {
648
- Executor: Executor,
649
- Client: Client,
650
- Request: Request,
651
- Response: Response,
646
+ Executor,
647
+ Client,
648
+ Request,
649
+ Response,
652
650
  // Exported for testing.
653
- buildPath: buildPath,
651
+ buildPath,
654
652
  }
package/lib/input.js CHANGED
@@ -31,6 +31,8 @@ const Button = {
31
31
  LEFT: 0,
32
32
  MIDDLE: 1,
33
33
  RIGHT: 2,
34
+ BACK: 3,
35
+ FORWARD: 4,
34
36
  }
35
37
 
36
38
  /**
@@ -159,7 +161,8 @@ class FileDetector {
159
161
  * @return {!Promise<string>} A promise for the processed file path.
160
162
  * @package
161
163
  */
162
- handleFile(driver, path) { // eslint-disable-line
164
+ handleFile(_driver, path) {
165
+ // eslint-disable-line
163
166
  return Promise.resolve(path)
164
167
  }
165
168
  }
@@ -801,7 +804,13 @@ class Actions {
801
804
  * @return {!Actions} a self reference.
802
805
  */
803
806
  sendKeys(...keys) {
807
+ const { WebElement } = require('./webdriver')
808
+
804
809
  const actions = []
810
+ if (keys.length > 1 && keys[0] instanceof WebElement) {
811
+ this.click(keys[0])
812
+ keys.shift()
813
+ }
805
814
  for (const key of keys) {
806
815
  if (typeof key === 'string') {
807
816
  for (const symbol of key) {
package/lib/promise.js CHANGED
@@ -22,16 +22,7 @@
22
22
 
23
23
  'use strict'
24
24
 
25
- /**
26
- * Determines whether a {@code value} should be treated as a promise.
27
- * Any object whose "then" property is a function will be considered a promise.
28
- *
29
- * @param {?} value The value to test.
30
- * @return {boolean} Whether the value is a promise.
31
- */
32
- function isPromise(value) {
33
- return Object.prototype.toString.call(value) === '[object Promise]'
34
- }
25
+ const { isObject, isPromise } = require('./util')
35
26
 
36
27
  /**
37
28
  * Creates a promise that will be resolved at a set time in the future.
@@ -218,7 +209,7 @@ async function fullyResolved(value) {
218
209
  return fullyResolveKeys(/** @type {!Array} */ (value))
219
210
  }
220
211
 
221
- if (value && typeof value === 'object') {
212
+ if (isObject(value)) {
222
213
  return fullyResolveKeys(/** @type {!Object} */ (value))
223
214
  }
224
215