android.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.AndroidWebView = exports.AndroidSocket = exports.AndroidInput = exports.AndroidDevice = exports.Android = void 0;
  6. var _fs = _interopRequireDefault(require("fs"));
  7. var _utils = require("../utils");
  8. var _events = require("./events");
  9. var _browserContext = require("./browserContext");
  10. var _channelOwner = require("./channelOwner");
  11. var _timeoutSettings = require("../common/timeoutSettings");
  12. var _waiter = require("./waiter");
  13. var _events2 = require("events");
  14. var _connection = require("./connection");
  15. var _errors = require("./errors");
  16. var _timeoutRunner = require("../utils/timeoutRunner");
  17. let _Symbol$asyncDispose;
  18. /**
  19. * Copyright (c) Microsoft Corporation.
  20. *
  21. * Licensed under the Apache License, Version 2.0 (the "License");
  22. * you may not use this file except in compliance with the License.
  23. * You may obtain a copy of the License at
  24. *
  25. * http://www.apache.org/licenses/LICENSE-2.0
  26. *
  27. * Unless required by applicable law or agreed to in writing, software
  28. * distributed under the License is distributed on an "AS IS" BASIS,
  29. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  30. * See the License for the specific language governing permissions and
  31. * limitations under the License.
  32. */
  33. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  34. class Android extends _channelOwner.ChannelOwner {
  35. static from(android) {
  36. return android._object;
  37. }
  38. constructor(parent, type, guid, initializer) {
  39. super(parent, type, guid, initializer);
  40. this._timeoutSettings = void 0;
  41. this._serverLauncher = void 0;
  42. this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
  43. }
  44. setDefaultTimeout(timeout) {
  45. this._timeoutSettings.setDefaultTimeout(timeout);
  46. this._channel.setDefaultTimeoutNoReply({
  47. timeout
  48. });
  49. }
  50. async devices(options = {}) {
  51. const {
  52. devices
  53. } = await this._channel.devices(options);
  54. return devices.map(d => AndroidDevice.from(d));
  55. }
  56. async launchServer(options = {}) {
  57. if (!this._serverLauncher) throw new Error('Launching server is not supported');
  58. return await this._serverLauncher.launchServer(options);
  59. }
  60. async connect(wsEndpoint, options = {}) {
  61. return await this._wrapApiCall(async () => {
  62. const deadline = options.timeout ? (0, _utils.monotonicTime)() + options.timeout : 0;
  63. const headers = {
  64. 'x-playwright-browser': 'android',
  65. ...options.headers
  66. };
  67. const localUtils = this._connection.localUtils();
  68. const connectParams = {
  69. wsEndpoint,
  70. headers,
  71. slowMo: options.slowMo,
  72. timeout: options.timeout
  73. };
  74. const {
  75. pipe
  76. } = await localUtils._channel.connect(connectParams);
  77. const closePipe = () => pipe.close().catch(() => {});
  78. const connection = new _connection.Connection(localUtils, this._instrumentation);
  79. connection.markAsRemote();
  80. connection.on('close', closePipe);
  81. let device;
  82. let closeError;
  83. const onPipeClosed = () => {
  84. var _device;
  85. (_device = device) === null || _device === void 0 ? void 0 : _device._didClose();
  86. connection.close(closeError);
  87. };
  88. pipe.on('closed', onPipeClosed);
  89. connection.onmessage = message => pipe.send({
  90. message
  91. }).catch(onPipeClosed);
  92. pipe.on('message', ({
  93. message
  94. }) => {
  95. try {
  96. connection.dispatch(message);
  97. } catch (e) {
  98. closeError = e;
  99. closePipe();
  100. }
  101. });
  102. const result = await (0, _timeoutRunner.raceAgainstDeadline)(async () => {
  103. const playwright = await connection.initializePlaywright();
  104. if (!playwright._initializer.preConnectedAndroidDevice) {
  105. closePipe();
  106. throw new Error('Malformed endpoint. Did you use Android.launchServer method?');
  107. }
  108. device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice);
  109. device._shouldCloseConnectionOnClose = true;
  110. device.on(_events.Events.AndroidDevice.Close, closePipe);
  111. return device;
  112. }, deadline);
  113. if (!result.timedOut) {
  114. return result.result;
  115. } else {
  116. closePipe();
  117. throw new Error(`Timeout ${options.timeout}ms exceeded`);
  118. }
  119. });
  120. }
  121. }
  122. exports.Android = Android;
  123. _Symbol$asyncDispose = Symbol.asyncDispose;
  124. class AndroidDevice extends _channelOwner.ChannelOwner {
  125. static from(androidDevice) {
  126. return androidDevice._object;
  127. }
  128. constructor(parent, type, guid, initializer) {
  129. super(parent, type, guid, initializer);
  130. this._timeoutSettings = void 0;
  131. this._webViews = new Map();
  132. this._shouldCloseConnectionOnClose = false;
  133. this.input = void 0;
  134. this.input = new AndroidInput(this);
  135. this._timeoutSettings = new _timeoutSettings.TimeoutSettings(parent._timeoutSettings);
  136. this._channel.on('webViewAdded', ({
  137. webView
  138. }) => this._onWebViewAdded(webView));
  139. this._channel.on('webViewRemoved', ({
  140. socketName
  141. }) => this._onWebViewRemoved(socketName));
  142. this._channel.on('close', () => this._didClose());
  143. }
  144. _onWebViewAdded(webView) {
  145. const view = new AndroidWebView(this, webView);
  146. this._webViews.set(webView.socketName, view);
  147. this.emit(_events.Events.AndroidDevice.WebView, view);
  148. }
  149. _onWebViewRemoved(socketName) {
  150. const view = this._webViews.get(socketName);
  151. this._webViews.delete(socketName);
  152. if (view) view.emit(_events.Events.AndroidWebView.Close);
  153. }
  154. setDefaultTimeout(timeout) {
  155. this._timeoutSettings.setDefaultTimeout(timeout);
  156. this._channel.setDefaultTimeoutNoReply({
  157. timeout
  158. });
  159. }
  160. serial() {
  161. return this._initializer.serial;
  162. }
  163. model() {
  164. return this._initializer.model;
  165. }
  166. webViews() {
  167. return [...this._webViews.values()];
  168. }
  169. async webView(selector, options) {
  170. const predicate = v => {
  171. if (selector.pkg) return v.pkg() === selector.pkg;
  172. if (selector.socketName) return v._socketName() === selector.socketName;
  173. return false;
  174. };
  175. const webView = [...this._webViews.values()].find(predicate);
  176. if (webView) return webView;
  177. return await this.waitForEvent('webview', {
  178. ...options,
  179. predicate
  180. });
  181. }
  182. async wait(selector, options) {
  183. await this._channel.wait({
  184. selector: toSelectorChannel(selector),
  185. ...options
  186. });
  187. }
  188. async fill(selector, text, options) {
  189. await this._channel.fill({
  190. selector: toSelectorChannel(selector),
  191. text,
  192. ...options
  193. });
  194. }
  195. async press(selector, key, options) {
  196. await this.tap(selector, options);
  197. await this.input.press(key);
  198. }
  199. async tap(selector, options) {
  200. await this._channel.tap({
  201. selector: toSelectorChannel(selector),
  202. ...options
  203. });
  204. }
  205. async drag(selector, dest, options) {
  206. await this._channel.drag({
  207. selector: toSelectorChannel(selector),
  208. dest,
  209. ...options
  210. });
  211. }
  212. async fling(selector, direction, options) {
  213. await this._channel.fling({
  214. selector: toSelectorChannel(selector),
  215. direction,
  216. ...options
  217. });
  218. }
  219. async longTap(selector, options) {
  220. await this._channel.longTap({
  221. selector: toSelectorChannel(selector),
  222. ...options
  223. });
  224. }
  225. async pinchClose(selector, percent, options) {
  226. await this._channel.pinchClose({
  227. selector: toSelectorChannel(selector),
  228. percent,
  229. ...options
  230. });
  231. }
  232. async pinchOpen(selector, percent, options) {
  233. await this._channel.pinchOpen({
  234. selector: toSelectorChannel(selector),
  235. percent,
  236. ...options
  237. });
  238. }
  239. async scroll(selector, direction, percent, options) {
  240. await this._channel.scroll({
  241. selector: toSelectorChannel(selector),
  242. direction,
  243. percent,
  244. ...options
  245. });
  246. }
  247. async swipe(selector, direction, percent, options) {
  248. await this._channel.swipe({
  249. selector: toSelectorChannel(selector),
  250. direction,
  251. percent,
  252. ...options
  253. });
  254. }
  255. async info(selector) {
  256. return (await this._channel.info({
  257. selector: toSelectorChannel(selector)
  258. })).info;
  259. }
  260. async screenshot(options = {}) {
  261. const {
  262. binary
  263. } = await this._channel.screenshot();
  264. if (options.path) await _fs.default.promises.writeFile(options.path, binary);
  265. return binary;
  266. }
  267. async [_Symbol$asyncDispose]() {
  268. await this.close();
  269. }
  270. async close() {
  271. try {
  272. if (this._shouldCloseConnectionOnClose) this._connection.close();else await this._channel.close();
  273. } catch (e) {
  274. if ((0, _errors.isTargetClosedError)(e)) return;
  275. throw e;
  276. }
  277. }
  278. _didClose() {
  279. this.emit(_events.Events.AndroidDevice.Close, this);
  280. }
  281. async shell(command) {
  282. const {
  283. result
  284. } = await this._channel.shell({
  285. command
  286. });
  287. return result;
  288. }
  289. async open(command) {
  290. return AndroidSocket.from((await this._channel.open({
  291. command
  292. })).socket);
  293. }
  294. async installApk(file, options) {
  295. await this._channel.installApk({
  296. file: await loadFile(file),
  297. args: options && options.args
  298. });
  299. }
  300. async push(file, path, options) {
  301. await this._channel.push({
  302. file: await loadFile(file),
  303. path,
  304. mode: options ? options.mode : undefined
  305. });
  306. }
  307. async launchBrowser(options = {}) {
  308. const contextOptions = await (0, _browserContext.prepareBrowserContextParams)(options);
  309. const result = await this._channel.launchBrowser(contextOptions);
  310. const context = _browserContext.BrowserContext.from(result.context);
  311. context._setOptions(contextOptions, {});
  312. return context;
  313. }
  314. async waitForEvent(event, optionsOrPredicate = {}) {
  315. return await this._wrapApiCall(async () => {
  316. const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
  317. const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
  318. const waiter = _waiter.Waiter.createForEvent(this, event);
  319. waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
  320. if (event !== _events.Events.AndroidDevice.Close) waiter.rejectOnEvent(this, _events.Events.AndroidDevice.Close, () => new _errors.TargetClosedError());
  321. const result = await waiter.waitForEvent(this, event, predicate);
  322. waiter.dispose();
  323. return result;
  324. });
  325. }
  326. }
  327. exports.AndroidDevice = AndroidDevice;
  328. class AndroidSocket extends _channelOwner.ChannelOwner {
  329. static from(androidDevice) {
  330. return androidDevice._object;
  331. }
  332. constructor(parent, type, guid, initializer) {
  333. super(parent, type, guid, initializer);
  334. this._channel.on('data', ({
  335. data
  336. }) => this.emit(_events.Events.AndroidSocket.Data, data));
  337. this._channel.on('close', () => this.emit(_events.Events.AndroidSocket.Close));
  338. }
  339. async write(data) {
  340. await this._channel.write({
  341. data
  342. });
  343. }
  344. async close() {
  345. await this._channel.close();
  346. }
  347. async [Symbol.asyncDispose]() {
  348. await this.close();
  349. }
  350. }
  351. exports.AndroidSocket = AndroidSocket;
  352. async function loadFile(file) {
  353. if ((0, _utils.isString)(file)) return await _fs.default.promises.readFile(file);
  354. return file;
  355. }
  356. class AndroidInput {
  357. constructor(device) {
  358. this._device = void 0;
  359. this._device = device;
  360. }
  361. async type(text) {
  362. await this._device._channel.inputType({
  363. text
  364. });
  365. }
  366. async press(key) {
  367. await this._device._channel.inputPress({
  368. key
  369. });
  370. }
  371. async tap(point) {
  372. await this._device._channel.inputTap({
  373. point
  374. });
  375. }
  376. async swipe(from, segments, steps) {
  377. await this._device._channel.inputSwipe({
  378. segments,
  379. steps
  380. });
  381. }
  382. async drag(from, to, steps) {
  383. await this._device._channel.inputDrag({
  384. from,
  385. to,
  386. steps
  387. });
  388. }
  389. }
  390. exports.AndroidInput = AndroidInput;
  391. function toSelectorChannel(selector) {
  392. const {
  393. checkable,
  394. checked,
  395. clazz,
  396. clickable,
  397. depth,
  398. desc,
  399. enabled,
  400. focusable,
  401. focused,
  402. hasChild,
  403. hasDescendant,
  404. longClickable,
  405. pkg,
  406. res,
  407. scrollable,
  408. selected,
  409. text
  410. } = selector;
  411. const toRegex = value => {
  412. if (value === undefined) return undefined;
  413. if ((0, _utils.isRegExp)(value)) return value.source;
  414. return '^' + value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d') + '$';
  415. };
  416. return {
  417. checkable,
  418. checked,
  419. clazz: toRegex(clazz),
  420. pkg: toRegex(pkg),
  421. desc: toRegex(desc),
  422. res: toRegex(res),
  423. text: toRegex(text),
  424. clickable,
  425. depth,
  426. enabled,
  427. focusable,
  428. focused,
  429. hasChild: hasChild ? {
  430. selector: toSelectorChannel(hasChild.selector)
  431. } : undefined,
  432. hasDescendant: hasDescendant ? {
  433. selector: toSelectorChannel(hasDescendant.selector),
  434. maxDepth: hasDescendant.maxDepth
  435. } : undefined,
  436. longClickable,
  437. scrollable,
  438. selected
  439. };
  440. }
  441. class AndroidWebView extends _events2.EventEmitter {
  442. constructor(device, data) {
  443. super();
  444. this._device = void 0;
  445. this._data = void 0;
  446. this._pagePromise = void 0;
  447. this._device = device;
  448. this._data = data;
  449. }
  450. pid() {
  451. return this._data.pid;
  452. }
  453. pkg() {
  454. return this._data.pkg;
  455. }
  456. _socketName() {
  457. return this._data.socketName;
  458. }
  459. async page() {
  460. if (!this._pagePromise) this._pagePromise = this._fetchPage();
  461. return await this._pagePromise;
  462. }
  463. async _fetchPage() {
  464. const {
  465. context
  466. } = await this._device._channel.connectToWebView({
  467. socketName: this._data.socketName
  468. });
  469. return _browserContext.BrowserContext.from(context).pages()[0];
  470. }
  471. }
  472. exports.AndroidWebView = AndroidWebView;