@allwright.dev/core 0.0.47 → 0.0.49

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/dist/bootstrap.js CHANGED
@@ -12,7 +12,7 @@ const ALLWRIGHT_HOME_ENV_VAR = "ALLWRIGHT_HOME";
12
12
  const ALLWRIGHT_REPOSITORY_ENV_VAR = "ALLWRIGHT_REPOSITORY";
13
13
  const ALLWRIGHT_VERSION_ENV_VAR = "ALLWRIGHT_VERSION";
14
14
  const DEFAULT_RELEASE_REPOSITORY = "allwright-dev/allwright";
15
- const DEFAULT_RELEASE_VERSION = "0.0.47";
15
+ const DEFAULT_RELEASE_VERSION = "0.0.49";
16
16
  const STARTUP_TIMEOUT_MS = 20_000;
17
17
  const PING_TIMEOUT_MS = 1_000;
18
18
  const PROTO_ROOT = fileURLToPath(new URL("../proto/", import.meta.url));
@@ -49,12 +49,11 @@ export async function ensureRuntimeReady(serverAddr) {
49
49
  managedServerStderr = "";
50
50
  managedServerSpawnError = null;
51
51
  }
52
- const cliPath = await ensureCliAvailable(expectedVersion);
53
- ensurePluginsInstalledWithCli(cliPath, expectedVersion, ["web"]);
54
52
  let resolvedServerAddr = serverAddr;
55
53
  if (status && status.version !== expectedVersion) {
56
54
  resolvedServerAddr = await allocateManagedServerAddr(serverAddr);
57
55
  }
56
+ const cliPath = await ensureCliAvailable(expectedVersion);
58
57
  managedServer = spawn(cliPath, ["serve", "--listen-addr", cliListenAddr(resolvedServerAddr)], {
59
58
  stdio: ["ignore", "pipe", "pipe"],
60
59
  });
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import { PageImpl } from "./page.js";
5
5
  import { setServerAddr, shutdown } from "./runtime.js";
6
6
  import type { Browser, BrowserKind, BrowserType, LaunchOptions, Page, ResolveConfigOptions, ResolvedAllwrightConfig } from "./types.js";
7
7
  export { findConfigFile, loadConfigFile, resolveConfig, setServerAddr, shutdown };
8
- export type { AllwrightConfig, Browser, BrowserInfo, BrowserKind, BrowserType, ClickResult, CommandOptions, CountResult, ElementResult, FillResult, HighlightOptions, HighlightResult, LaunchOptions, MobileAndroidConnectOptions, MobileAndroidDevice, MobileAndroidLaunchOptions, MobileAndroidPage, Locator, LocatorInfo, MobileSurfaceNamespace, NavigateResult, Page, PageInfo, PressOptions, PressResult, ResolveConfigOptions, ResolvedAllwrightConfig, RetryConfig, TextResult, WaitForSelectorOptions, WaitForSelectorResult, } from "./types.js";
8
+ export type { AllwrightConfig, Browser, BrowserInfo, BrowserKind, BrowserType, ClickResult, CommandOptions, CountResult, ElementResult, FillResult, HighlightOptions, HighlightResult, LaunchOptions, MobileAndroidConnectOptions, MobileAndroidDevice, MobileAndroidLaunchOptions, MobileAndroidLocator, MobileAndroidPage, Locator, LocatorInfo, MobileSurfaceNamespace, NavigateResult, Page, PageInfo, PressOptions, PressResult, ResolveConfigOptions, ResolvedAllwrightConfig, RetryConfig, TextResult, WaitForSelectorOptions, WaitForSelectorResult, } from "./types.js";
9
9
  export declare const chromium: BrowserType;
10
10
  export declare const firefox: BrowserType;
11
11
  export { mobile };
package/dist/mobile.js CHANGED
@@ -1,4 +1,5 @@
1
- import { ensurePluginsInstalled, invokePlugin } from "./bootstrap.js";
1
+ import { invokePlugin } from "./bootstrap.js";
2
+ import { chainMobileSelectorForTransport, normalizeMobileSelectorForTransport } from "./mobileSelectors.js";
2
3
  function timeoutMsOf(options) {
3
4
  return options?.timeoutMs;
4
5
  }
@@ -22,12 +23,15 @@ class MobileAndroidPageImpl {
22
23
  get sessionId() {
23
24
  return this.pageSession.page_id;
24
25
  }
26
+ locator(selector) {
27
+ return new MobileAndroidLocatorImpl(this, normalizeMobileSelectorForTransport(selector));
28
+ }
25
29
  async click(selector, options = {}) {
26
30
  const result = await invokeAndroidExpected("click", {
27
31
  command: "click_element",
28
32
  browser_session: this.browserSession,
29
33
  page_session: this.pageSession,
30
- selector,
34
+ selector: normalizeMobileSelectorForTransport(selector),
31
35
  timeout_ms: timeoutMsOf(options),
32
36
  });
33
37
  return {
@@ -41,7 +45,7 @@ class MobileAndroidPageImpl {
41
45
  command: "fill_element",
42
46
  browser_session: this.browserSession,
43
47
  page_session: this.pageSession,
44
- selector,
48
+ selector: normalizeMobileSelectorForTransport(selector),
45
49
  value,
46
50
  timeout_ms: timeoutMsOf(options),
47
51
  });
@@ -52,6 +56,23 @@ class MobileAndroidPageImpl {
52
56
  };
53
57
  }
54
58
  }
59
+ class MobileAndroidLocatorImpl {
60
+ page;
61
+ selector;
62
+ constructor(page, selector) {
63
+ this.page = page;
64
+ this.selector = selector;
65
+ }
66
+ async click(options = {}) {
67
+ return this.page.click(this.selector, options);
68
+ }
69
+ async fill(value, options = {}) {
70
+ return this.page.fill(this.selector, value, options);
71
+ }
72
+ locator(selector) {
73
+ return new MobileAndroidLocatorImpl(this.page, chainMobileSelectorForTransport(this.selector, selector));
74
+ }
75
+ }
55
76
  class MobileAndroidDeviceImpl {
56
77
  connectInfoRaw;
57
78
  #initialPage;
@@ -86,7 +107,6 @@ class MobileAndroidDeviceImpl {
86
107
  }
87
108
  class MobileAndroidSurfaceImpl {
88
109
  async connect(options = {}) {
89
- await ensurePluginsInstalled(["mobile-android"]);
90
110
  const connectInfo = await invokeAndroidExpected("connect", {
91
111
  command: "connect",
92
112
  platform: "android",
@@ -0,0 +1,2 @@
1
+ export declare function normalizeMobileSelectorForTransport(selector: string): string;
2
+ export declare function chainMobileSelectorForTransport(parent: string, child: string): string;
@@ -0,0 +1,168 @@
1
+ const UIAUTOMATOR_SELECTOR_KEYS = new Set([
2
+ "text",
3
+ "textcontains",
4
+ "textmatches",
5
+ "textstartswith",
6
+ "classname",
7
+ "classnamematches",
8
+ "description",
9
+ "desc",
10
+ "descriptioncontains",
11
+ "desccontains",
12
+ "descriptionmatches",
13
+ "descmatches",
14
+ "descriptionstartswith",
15
+ "descstartswith",
16
+ "checkable",
17
+ "checked",
18
+ "clickable",
19
+ "longclickable",
20
+ "scrollable",
21
+ "enabled",
22
+ "focusable",
23
+ "focused",
24
+ "selected",
25
+ "packagename",
26
+ "package",
27
+ "packagenamematches",
28
+ "resourceid",
29
+ "resourceidmatches",
30
+ "index",
31
+ "instance",
32
+ ]);
33
+ const SELECTOR_PREFIXES = ["xpath=", "xpath:", "css=", "css:", "uia=", "uia:"];
34
+ function decodeSelectorBody(body) {
35
+ const candidate = body.trim();
36
+ if (candidate.startsWith("\"") && candidate.endsWith("\"")) {
37
+ try {
38
+ return JSON.parse(candidate);
39
+ }
40
+ catch {
41
+ return candidate;
42
+ }
43
+ }
44
+ return candidate;
45
+ }
46
+ function parseExplicitSelectorPrefix(selector) {
47
+ const lowered = selector.toLowerCase();
48
+ if (lowered.startsWith("xpath=") || lowered.startsWith("xpath:")) {
49
+ return { flavor: "xpath", prefixLength: 6 };
50
+ }
51
+ if (lowered.startsWith("css=") || lowered.startsWith("css:")) {
52
+ return { flavor: "css", prefixLength: 4 };
53
+ }
54
+ if (lowered.startsWith("uia=") || lowered.startsWith("uia:")) {
55
+ return { flavor: "uia", prefixLength: 4 };
56
+ }
57
+ return null;
58
+ }
59
+ function findJsonStringEnd(value) {
60
+ if (!value.startsWith("\"")) {
61
+ return null;
62
+ }
63
+ let escaped = false;
64
+ for (let index = 1; index < value.length; index += 1) {
65
+ const char = value[index];
66
+ if (escaped) {
67
+ escaped = false;
68
+ continue;
69
+ }
70
+ if (char === "\\") {
71
+ escaped = true;
72
+ continue;
73
+ }
74
+ if (char === "\"") {
75
+ return index + 1;
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+ function isNormalizedTransportSelector(selector) {
81
+ const trimmed = selector.trim();
82
+ if (!trimmed) {
83
+ return false;
84
+ }
85
+ let index = 0;
86
+ while (index < trimmed.length) {
87
+ const prefix = parseExplicitSelectorPrefix(trimmed.slice(index));
88
+ if (!prefix) {
89
+ return false;
90
+ }
91
+ index += prefix.prefixLength;
92
+ const remainder = trimmed.slice(index);
93
+ if (!remainder.startsWith("\"")) {
94
+ return false;
95
+ }
96
+ const jsonEnd = findJsonStringEnd(remainder);
97
+ if (!jsonEnd) {
98
+ return false;
99
+ }
100
+ index += jsonEnd;
101
+ const tail = trimmed.slice(index);
102
+ if (!tail) {
103
+ return true;
104
+ }
105
+ if (!/^\s+/.test(tail)) {
106
+ return false;
107
+ }
108
+ const nextIndex = index + tail.match(/^\s+/)?.[0].length;
109
+ const nextSegment = trimmed.slice(nextIndex).toLowerCase();
110
+ if (!SELECTOR_PREFIXES.some((prefixValue) => nextSegment.startsWith(prefixValue))) {
111
+ return false;
112
+ }
113
+ index = nextIndex;
114
+ }
115
+ return true;
116
+ }
117
+ function parseUiAutomatorSelectorPrefix(selector) {
118
+ const separatorIndex = selector.search(/[=:]/);
119
+ if (separatorIndex <= 0) {
120
+ return null;
121
+ }
122
+ const key = selector.slice(0, separatorIndex).trim().toLowerCase();
123
+ if (!UIAUTOMATOR_SELECTOR_KEYS.has(key)) {
124
+ return null;
125
+ }
126
+ return separatorIndex + 1;
127
+ }
128
+ function parseSelectorForTransport(selector) {
129
+ const trimmed = selector.trim();
130
+ const explicit = parseExplicitSelectorPrefix(trimmed);
131
+ if (explicit) {
132
+ return { flavor: explicit.flavor, body: decodeSelectorBody(trimmed.slice(explicit.prefixLength)) };
133
+ }
134
+ const uiAutomatorPrefix = parseUiAutomatorSelectorPrefix(trimmed);
135
+ if (uiAutomatorPrefix) {
136
+ return { flavor: "uia", body: `${trimmed.slice(0, uiAutomatorPrefix - 1)}=${trimmed.slice(uiAutomatorPrefix)}` };
137
+ }
138
+ if (trimmed.startsWith("//") ||
139
+ trimmed.startsWith(".//") ||
140
+ trimmed.startsWith("../") ||
141
+ trimmed.startsWith("/") ||
142
+ trimmed.startsWith("(")) {
143
+ return { flavor: "xpath", body: trimmed };
144
+ }
145
+ return { flavor: "css", body: trimmed };
146
+ }
147
+ export function normalizeMobileSelectorForTransport(selector) {
148
+ const trimmed = selector.trim();
149
+ if (!trimmed) {
150
+ return "";
151
+ }
152
+ if (isNormalizedTransportSelector(trimmed)) {
153
+ return trimmed;
154
+ }
155
+ const parsed = parseSelectorForTransport(selector);
156
+ return `${parsed.flavor}=${JSON.stringify(parsed.body)}`;
157
+ }
158
+ export function chainMobileSelectorForTransport(parent, child) {
159
+ const parentSelector = normalizeMobileSelectorForTransport(parent);
160
+ const childSelector = normalizeMobileSelectorForTransport(child);
161
+ if (!parentSelector) {
162
+ return childSelector;
163
+ }
164
+ if (!childSelector) {
165
+ return parentSelector;
166
+ }
167
+ return `${parentSelector} ${childSelector}`;
168
+ }
package/dist/types.d.ts CHANGED
@@ -127,8 +127,16 @@ export interface MobileAndroidLaunchOptions {
127
127
  stopBeforeLaunch?: boolean;
128
128
  timeoutMs?: number;
129
129
  }
130
+ export interface MobileAndroidLocator {
131
+ readonly page: MobileAndroidPage;
132
+ readonly selector: string;
133
+ click(options?: CommandOptions): Promise<ClickResult>;
134
+ fill(value: string, options?: CommandOptions): Promise<FillResult>;
135
+ locator(selector: string): MobileAndroidLocator;
136
+ }
130
137
  export interface MobileAndroidPage {
131
138
  readonly sessionId: string;
139
+ locator(selector: string): MobileAndroidLocator;
132
140
  click(selector: string, options?: CommandOptions): Promise<ClickResult>;
133
141
  fill(selector: string, value: string, options?: CommandOptions): Promise<FillResult>;
134
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allwright.dev/core",
3
- "version": "0.0.47",
3
+ "version": "0.0.49",
4
4
  "description": "High-level TypeScript client for the allwright automation engine.",
5
5
  "license": "MIT",
6
6
  "type": "module",