android.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. "use strict";
  2. var __defProp = Object.defineProperty;
  3. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  4. var __getOwnPropNames = Object.getOwnPropertyNames;
  5. var __hasOwnProp = Object.prototype.hasOwnProperty;
  6. var __export = (target, all) => {
  7. for (var name in all)
  8. __defProp(target, name, { get: all[name], enumerable: true });
  9. };
  10. var __copyProps = (to, from, except, desc) => {
  11. if (from && typeof from === "object" || typeof from === "function") {
  12. for (let key of __getOwnPropNames(from))
  13. if (!__hasOwnProp.call(to, key) && key !== except)
  14. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  15. }
  16. return to;
  17. };
  18. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  19. var android_exports = {};
  20. __export(android_exports, {
  21. Android: () => Android,
  22. AndroidDevice: () => AndroidDevice,
  23. AndroidInput: () => AndroidInput,
  24. AndroidSocket: () => AndroidSocket,
  25. AndroidWebView: () => AndroidWebView
  26. });
  27. module.exports = __toCommonJS(android_exports);
  28. var import_eventEmitter = require("./eventEmitter");
  29. var import_browserContext = require("./browserContext");
  30. var import_channelOwner = require("./channelOwner");
  31. var import_errors = require("./errors");
  32. var import_events = require("./events");
  33. var import_waiter = require("./waiter");
  34. var import_timeoutSettings = require("./timeoutSettings");
  35. var import_rtti = require("../utils/isomorphic/rtti");
  36. var import_time = require("../utils/isomorphic/time");
  37. var import_timeoutRunner = require("../utils/isomorphic/timeoutRunner");
  38. var import_webSocket = require("./webSocket");
  39. class Android extends import_channelOwner.ChannelOwner {
  40. static from(android) {
  41. return android._object;
  42. }
  43. constructor(parent, type, guid, initializer) {
  44. super(parent, type, guid, initializer);
  45. this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
  46. }
  47. setDefaultTimeout(timeout) {
  48. this._timeoutSettings.setDefaultTimeout(timeout);
  49. }
  50. async devices(options = {}) {
  51. const { devices } = await this._channel.devices(options);
  52. return devices.map((d) => AndroidDevice.from(d));
  53. }
  54. async launchServer(options = {}) {
  55. if (!this._serverLauncher)
  56. throw new Error("Launching server is not supported");
  57. return await this._serverLauncher.launchServer(options);
  58. }
  59. async connect(wsEndpoint, options = {}) {
  60. return await this._wrapApiCall(async () => {
  61. const deadline = options.timeout ? (0, import_time.monotonicTime)() + options.timeout : 0;
  62. const headers = { "x-playwright-browser": "android", ...options.headers };
  63. const connectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout || 0 };
  64. const connection = await (0, import_webSocket.connectOverWebSocket)(this._connection, connectParams);
  65. let device;
  66. connection.on("close", () => {
  67. device?._didClose();
  68. });
  69. const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
  70. const playwright = await connection.initializePlaywright();
  71. if (!playwright._initializer.preConnectedAndroidDevice) {
  72. connection.close();
  73. throw new Error("Malformed endpoint. Did you use Android.launchServer method?");
  74. }
  75. device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice);
  76. device._shouldCloseConnectionOnClose = true;
  77. device.on(import_events.Events.AndroidDevice.Close, () => connection.close());
  78. return device;
  79. }, deadline);
  80. if (!result.timedOut) {
  81. return result.result;
  82. } else {
  83. connection.close();
  84. throw new Error(`Timeout ${options.timeout}ms exceeded`);
  85. }
  86. });
  87. }
  88. }
  89. class AndroidDevice extends import_channelOwner.ChannelOwner {
  90. constructor(parent, type, guid, initializer) {
  91. super(parent, type, guid, initializer);
  92. this._webViews = /* @__PURE__ */ new Map();
  93. this._shouldCloseConnectionOnClose = false;
  94. this._android = parent;
  95. this.input = new AndroidInput(this);
  96. this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform, parent._timeoutSettings);
  97. this._channel.on("webViewAdded", ({ webView }) => this._onWebViewAdded(webView));
  98. this._channel.on("webViewRemoved", ({ socketName }) => this._onWebViewRemoved(socketName));
  99. this._channel.on("close", () => this._didClose());
  100. }
  101. static from(androidDevice) {
  102. return androidDevice._object;
  103. }
  104. _onWebViewAdded(webView) {
  105. const view = new AndroidWebView(this, webView);
  106. this._webViews.set(webView.socketName, view);
  107. this.emit(import_events.Events.AndroidDevice.WebView, view);
  108. }
  109. _onWebViewRemoved(socketName) {
  110. const view = this._webViews.get(socketName);
  111. this._webViews.delete(socketName);
  112. if (view)
  113. view.emit(import_events.Events.AndroidWebView.Close);
  114. }
  115. setDefaultTimeout(timeout) {
  116. this._timeoutSettings.setDefaultTimeout(timeout);
  117. }
  118. serial() {
  119. return this._initializer.serial;
  120. }
  121. model() {
  122. return this._initializer.model;
  123. }
  124. webViews() {
  125. return [...this._webViews.values()];
  126. }
  127. async webView(selector, options) {
  128. const predicate = (v) => {
  129. if (selector.pkg)
  130. return v.pkg() === selector.pkg;
  131. if (selector.socketName)
  132. return v._socketName() === selector.socketName;
  133. return false;
  134. };
  135. const webView = [...this._webViews.values()].find(predicate);
  136. if (webView)
  137. return webView;
  138. return await this.waitForEvent("webview", { ...options, predicate });
  139. }
  140. async wait(selector, options = {}) {
  141. await this._channel.wait({ androidSelector: toSelectorChannel(selector), ...options, timeout: this._timeoutSettings.timeout(options) });
  142. }
  143. async fill(selector, text, options = {}) {
  144. await this._channel.fill({ androidSelector: toSelectorChannel(selector), text, ...options, timeout: this._timeoutSettings.timeout(options) });
  145. }
  146. async press(selector, key, options = {}) {
  147. await this.tap(selector, options);
  148. await this.input.press(key);
  149. }
  150. async tap(selector, options = {}) {
  151. await this._channel.tap({ androidSelector: toSelectorChannel(selector), ...options, timeout: this._timeoutSettings.timeout(options) });
  152. }
  153. async drag(selector, dest, options = {}) {
  154. await this._channel.drag({ androidSelector: toSelectorChannel(selector), dest, ...options, timeout: this._timeoutSettings.timeout(options) });
  155. }
  156. async fling(selector, direction, options = {}) {
  157. await this._channel.fling({ androidSelector: toSelectorChannel(selector), direction, ...options, timeout: this._timeoutSettings.timeout(options) });
  158. }
  159. async longTap(selector, options = {}) {
  160. await this._channel.longTap({ androidSelector: toSelectorChannel(selector), ...options, timeout: this._timeoutSettings.timeout(options) });
  161. }
  162. async pinchClose(selector, percent, options = {}) {
  163. await this._channel.pinchClose({ androidSelector: toSelectorChannel(selector), percent, ...options, timeout: this._timeoutSettings.timeout(options) });
  164. }
  165. async pinchOpen(selector, percent, options = {}) {
  166. await this._channel.pinchOpen({ androidSelector: toSelectorChannel(selector), percent, ...options, timeout: this._timeoutSettings.timeout(options) });
  167. }
  168. async scroll(selector, direction, percent, options = {}) {
  169. await this._channel.scroll({ androidSelector: toSelectorChannel(selector), direction, percent, ...options, timeout: this._timeoutSettings.timeout(options) });
  170. }
  171. async swipe(selector, direction, percent, options = {}) {
  172. await this._channel.swipe({ androidSelector: toSelectorChannel(selector), direction, percent, ...options, timeout: this._timeoutSettings.timeout(options) });
  173. }
  174. async info(selector) {
  175. return (await this._channel.info({ androidSelector: toSelectorChannel(selector) })).info;
  176. }
  177. async screenshot(options = {}) {
  178. const { binary } = await this._channel.screenshot();
  179. if (options.path)
  180. await this._platform.fs().promises.writeFile(options.path, binary);
  181. return binary;
  182. }
  183. async [Symbol.asyncDispose]() {
  184. await this.close();
  185. }
  186. async close() {
  187. try {
  188. if (this._shouldCloseConnectionOnClose)
  189. this._connection.close();
  190. else
  191. await this._channel.close();
  192. } catch (e) {
  193. if ((0, import_errors.isTargetClosedError)(e))
  194. return;
  195. throw e;
  196. }
  197. }
  198. _didClose() {
  199. this.emit(import_events.Events.AndroidDevice.Close, this);
  200. }
  201. async shell(command) {
  202. const { result } = await this._channel.shell({ command });
  203. return result;
  204. }
  205. async open(command) {
  206. return AndroidSocket.from((await this._channel.open({ command })).socket);
  207. }
  208. async installApk(file, options) {
  209. await this._channel.installApk({ file: await loadFile(this._platform, file), args: options && options.args });
  210. }
  211. async push(file, path, options) {
  212. await this._channel.push({ file: await loadFile(this._platform, file), path, mode: options ? options.mode : void 0 });
  213. }
  214. async launchBrowser(options = {}) {
  215. const contextOptions = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
  216. const result = await this._channel.launchBrowser(contextOptions);
  217. const context = import_browserContext.BrowserContext.from(result.context);
  218. const selectors = this._android._playwright.selectors;
  219. selectors._contextsForSelectors.add(context);
  220. context.once(import_events.Events.BrowserContext.Close, () => selectors._contextsForSelectors.delete(context));
  221. await context._initializeHarFromOptions(options.recordHar);
  222. return context;
  223. }
  224. async waitForEvent(event, optionsOrPredicate = {}) {
  225. return await this._wrapApiCall(async () => {
  226. const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
  227. const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
  228. const waiter = import_waiter.Waiter.createForEvent(this, event);
  229. waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
  230. if (event !== import_events.Events.AndroidDevice.Close)
  231. waiter.rejectOnEvent(this, import_events.Events.AndroidDevice.Close, () => new import_errors.TargetClosedError());
  232. const result = await waiter.waitForEvent(this, event, predicate);
  233. waiter.dispose();
  234. return result;
  235. });
  236. }
  237. }
  238. class AndroidSocket extends import_channelOwner.ChannelOwner {
  239. static from(androidDevice) {
  240. return androidDevice._object;
  241. }
  242. constructor(parent, type, guid, initializer) {
  243. super(parent, type, guid, initializer);
  244. this._channel.on("data", ({ data }) => this.emit(import_events.Events.AndroidSocket.Data, data));
  245. this._channel.on("close", () => this.emit(import_events.Events.AndroidSocket.Close));
  246. }
  247. async write(data) {
  248. await this._channel.write({ data });
  249. }
  250. async close() {
  251. await this._channel.close();
  252. }
  253. async [Symbol.asyncDispose]() {
  254. await this.close();
  255. }
  256. }
  257. async function loadFile(platform, file) {
  258. if ((0, import_rtti.isString)(file))
  259. return await platform.fs().promises.readFile(file);
  260. return file;
  261. }
  262. class AndroidInput {
  263. constructor(device) {
  264. this._device = device;
  265. }
  266. async type(text) {
  267. await this._device._channel.inputType({ text });
  268. }
  269. async press(key) {
  270. await this._device._channel.inputPress({ key });
  271. }
  272. async tap(point) {
  273. await this._device._channel.inputTap({ point });
  274. }
  275. async swipe(from, segments, steps) {
  276. await this._device._channel.inputSwipe({ segments, steps });
  277. }
  278. async drag(from, to, steps) {
  279. await this._device._channel.inputDrag({ from, to, steps });
  280. }
  281. }
  282. function toSelectorChannel(selector) {
  283. const {
  284. checkable,
  285. checked,
  286. clazz,
  287. clickable,
  288. depth,
  289. desc,
  290. enabled,
  291. focusable,
  292. focused,
  293. hasChild,
  294. hasDescendant,
  295. longClickable,
  296. pkg,
  297. res,
  298. scrollable,
  299. selected,
  300. text
  301. } = selector;
  302. const toRegex = (value) => {
  303. if (value === void 0)
  304. return void 0;
  305. if ((0, import_rtti.isRegExp)(value))
  306. return value.source;
  307. return "^" + value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d") + "$";
  308. };
  309. return {
  310. checkable,
  311. checked,
  312. clazz: toRegex(clazz),
  313. pkg: toRegex(pkg),
  314. desc: toRegex(desc),
  315. res: toRegex(res),
  316. text: toRegex(text),
  317. clickable,
  318. depth,
  319. enabled,
  320. focusable,
  321. focused,
  322. hasChild: hasChild ? { androidSelector: toSelectorChannel(hasChild.selector) } : void 0,
  323. hasDescendant: hasDescendant ? { androidSelector: toSelectorChannel(hasDescendant.selector), maxDepth: hasDescendant.maxDepth } : void 0,
  324. longClickable,
  325. scrollable,
  326. selected
  327. };
  328. }
  329. class AndroidWebView extends import_eventEmitter.EventEmitter {
  330. constructor(device, data) {
  331. super(device._platform);
  332. this._device = device;
  333. this._data = data;
  334. }
  335. pid() {
  336. return this._data.pid;
  337. }
  338. pkg() {
  339. return this._data.pkg;
  340. }
  341. _socketName() {
  342. return this._data.socketName;
  343. }
  344. async page() {
  345. if (!this._pagePromise)
  346. this._pagePromise = this._fetchPage();
  347. return await this._pagePromise;
  348. }
  349. async _fetchPage() {
  350. const { context } = await this._device._channel.connectToWebView({ socketName: this._data.socketName });
  351. return import_browserContext.BrowserContext.from(context).pages()[0];
  352. }
  353. }
  354. // Annotate the CommonJS export names for ESM import in node:
  355. 0 && (module.exports = {
  356. Android,
  357. AndroidDevice,
  358. AndroidInput,
  359. AndroidSocket,
  360. AndroidWebView
  361. });