@taqwright/taqwright 0.0.25 → 0.0.26

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.
@@ -431,6 +431,16 @@ export const INSPECTOR_HTML = `<!doctype html>
431
431
  background: #000; box-shadow: 0 6px 22px rgba(0,0,0,0.10); }
432
432
  #screen-img { display: block; max-width: 100%; max-height: calc(100vh - 100px);
433
433
  user-select: none; -webkit-user-drag: none; }
434
+ /* Graceful fallback when a snapshot fails / returns no screenshot — shown
435
+ instead of the browser's broken-image glyph. */
436
+ .screen-unavailable-msg { display: none; box-sizing: border-box; width: 300px;
437
+ max-width: 100%; min-height: 480px; max-height: calc(100vh - 100px);
438
+ flex-direction: column; align-items: center; justify-content: center; gap: 8px;
439
+ padding: 24px; text-align: center; color: var(--text-dim); }
440
+ .screen-unavailable-title { font-size: 14px; font-weight: 600; color: var(--text); }
441
+ .screen-unavailable-sub { font-size: 12.5px; }
442
+ #screen-host.screen-unavailable #screen-img { display: none; }
443
+ #screen-host.screen-unavailable .screen-unavailable-msg { display: flex; }
434
444
  #highlight { position: absolute; border: 2px solid var(--accent);
435
445
  background: rgba(9,105,218,0.12); box-shadow: 0 0 0 9999px rgba(0,0,0,0.40) inset;
436
446
  pointer-events: none; transition: all 0.12s ease-out; }
@@ -1289,6 +1299,10 @@ export const INSPECTOR_HTML = `<!doctype html>
1289
1299
  </div>
1290
1300
  <div id="screen-host">
1291
1301
  <img id="screen-img" alt="device screen" />
1302
+ <div class="screen-unavailable-msg">
1303
+ <div class="screen-unavailable-title">Device screen unavailable</div>
1304
+ <div class="screen-unavailable-sub">Couldn't capture the device — retrying…</div>
1305
+ </div>
1292
1306
  <div id="highlight" style="display:none"></div>
1293
1307
  <div id="screen-action-overlay" class="screen-action-overlay" aria-hidden="true">
1294
1308
  <div class="screen-action-card">
@@ -2036,6 +2050,12 @@ await mobile.getByUiSelector('new UiSelector().description("Login")').click();</
2036
2050
  if (autoRefreshOn) scheduleNextRefresh(Math.max(base, elapsed));
2037
2051
  }
2038
2052
 
2053
+ // Toggle the "device screen unavailable" fallback (shown when a snapshot
2054
+ // fails or returns no screenshot, so we never render a broken <img>).
2055
+ function setScreenUnavailable(on) {
2056
+ $('screen-host').classList.toggle('screen-unavailable', !!on);
2057
+ }
2058
+
2039
2059
  async function fetchSnapshot(opts) {
2040
2060
  const force = opts && opts.force;
2041
2061
  if (snapshotInFlight) {
@@ -2065,7 +2085,14 @@ await mobile.getByUiSelector('new UiSelector().description("Login")').click();</
2065
2085
  state.sourceXml = j.source;
2066
2086
  $('session-meta').textContent = formatSessionMeta(j.platform, j.project);
2067
2087
  $('screen-meta').textContent = j.viewport.w + ' × ' + j.viewport.h;
2068
- $('screen-img').src = 'data:image/png;base64,' + j.screenshot;
2088
+ // Only set the image when there's an actual screenshot — an empty/missing
2089
+ // one would render as a broken <img>; show the fallback instead.
2090
+ if (typeof j.screenshot === 'string' && j.screenshot.length > 0) {
2091
+ $('screen-img').src = 'data:image/png;base64,' + j.screenshot;
2092
+ setScreenUnavailable(false);
2093
+ } else {
2094
+ setScreenUnavailable(true);
2095
+ }
2069
2096
  renderTree();
2070
2097
  if (hierarchyMode === 'xml') refreshHierarchyXml();
2071
2098
  if (prevXpath && prevSig) {
@@ -2089,6 +2116,7 @@ await mobile.getByUiSelector('new UiSelector().description("Login")').click();</
2089
2116
  setStatus('idle');
2090
2117
  } catch (err) {
2091
2118
  setStatus('error: ' + err.message);
2119
+ setScreenUnavailable(true);
2092
2120
  } finally {
2093
2121
  snapshotInFlight = false;
2094
2122
  }
@@ -2716,6 +2744,10 @@ await mobile.getByUiSelector('new UiSelector().description("Login")').click();</
2716
2744
  };
2717
2745
  }
2718
2746
 
2747
+ // A corrupt/truncated data URI fails to decode — fall back rather than show
2748
+ // the browser's broken-image glyph.
2749
+ $('screen-img').addEventListener('error', () => setScreenUnavailable(true));
2750
+
2719
2751
  $('screen-img').addEventListener('mouseup', (ev) => {
2720
2752
  const pt = imgToDevice(ev);
2721
2753
  // Pick mode (Record tab) takes priority — consume one click then dismiss.
@@ -4250,8 +4282,22 @@ await mobile.getByUiSelector('new UiSelector().description("Login")').click();</
4250
4282
  updateConnectSummary();
4251
4283
  }
4252
4284
 
4285
+ // Whether the wizard is allowed to advance forward off the given step (its
4286
+ // prerequisites are met). Mirrors the gating in updateConnectSummary.
4287
+ function canAdvanceFrom(step) {
4288
+ if (step === 1) {
4289
+ return isCloudMode() ? cloudCredsValid : $('appium-pill').classList.contains('live');
4290
+ }
4291
+ if (step === 2) return !!$('cap-device').value.trim();
4292
+ return true;
4293
+ }
4294
+
4253
4295
  function goToStep(n) {
4254
4296
  if (n < 1 || n > 3) return;
4297
+ // Hard-gate forward navigation: never advance past a step whose
4298
+ // prerequisites aren't met — even for programmatic callers like the guided
4299
+ // tour. Backward navigation and re-selecting the current step are free.
4300
+ if (n > wizardStep && !canAdvanceFrom(wizardStep)) return;
4255
4301
  wizardStep = n;
4256
4302
  document.querySelectorAll('.wizard-page').forEach((p) => {
4257
4303
  p.classList.toggle('active', Number(p.getAttribute('data-page')) === n);
@@ -5188,6 +5234,34 @@ await mobile.getByUiSelector('new UiSelector().description("Login")').click();</
5188
5234
  const t = document.querySelector('.tab[data-tab="' + name + '"]');
5189
5235
  if (t) t.click();
5190
5236
  }
5237
+ // Live tour only: the Locators / Attributes panels are empty until an element
5238
+ // is selected, so the tour would spotlight a blank panel. If the user hasn't
5239
+ // selected anything yet, auto-select a representative node (one with an id /
5240
+ // text / content-desc, else the first node) so those steps show real content.
5241
+ function tourEnsureSelection() {
5242
+ if (state.selected) return;
5243
+ let pick = null;
5244
+ for (const [, el] of state.nodeMap) {
5245
+ if (!el || !el.getAttribute) continue;
5246
+ if (
5247
+ el.getAttribute('resource-id') ||
5248
+ el.getAttribute('text') ||
5249
+ el.getAttribute('content-desc') ||
5250
+ el.getAttribute('name') ||
5251
+ el.getAttribute('label')
5252
+ ) {
5253
+ pick = el;
5254
+ break;
5255
+ }
5256
+ }
5257
+ if (!pick) {
5258
+ for (const [, el] of state.nodeMap) {
5259
+ pick = el;
5260
+ break;
5261
+ }
5262
+ }
5263
+ if (pick) selectElement(pick);
5264
+ }
5191
5265
  // Switch the demo stage's mock right-hand tab (Record / Script / Locators / Attributes).
5192
5266
  function showDemoTab(name) {
5193
5267
  ['rec', 'script', 'loc', 'attrs'].forEach((k) => {
@@ -5230,9 +5304,9 @@ await mobile.getByUiSelector('new UiSelector().description("Login")').click();</
5230
5304
  body: 'Press <b>Start record</b>, select an element, then choose an action — Click, Type, Clear, gestures… The <b>Actions / Screen / Assertions</b> sub-tabs switch what you capture. Each step is appended live.' },
5231
5305
  { sel: '#tab-script', before: function () { tourClickTab('script'); }, title: 'Recorded script',
5232
5306
  body: 'Your test in <b>Taqwright</b> (runnable), or <b>Python</b> / <b>Java</b> (steps only). Use <b>⎘ Copy</b>, <b>↓ Export</b> (saves into your tests folder), or Clear.' },
5233
- { sel: '#tab-locators', before: function () { tourClickTab('locators'); }, title: 'Locators',
5307
+ { sel: '#tab-locators', before: function () { tourEnsureSelection(); tourClickTab('locators'); }, title: 'Locators',
5234
5308
  body: 'Ranked, uniqueness-verified selectors for the selected element — id, accessibility id, UIAutomator / NSPredicate / Class Chain, xpath. The <b>recommended</b> pick is on top; click any to copy.' },
5235
- { sel: '#tab-attrs', before: function () { tourClickTab('attrs'); }, title: 'Attributes',
5309
+ { sel: '#tab-attrs', before: function () { tourEnsureSelection(); tourClickTab('attrs'); }, title: 'Attributes',
5236
5310
  body: 'The selected element\\'s full attribute set (resource-id, class, text, content-desc, bounds…) plus its xpath.' },
5237
5311
  { sel: '#btn-disconnect', before: function () { tourClickTab('record'); }, title: 'Done',
5238
5312
  body: 'When finished, <b>Disconnect</b> ends the session and returns to setup. Reopen this tour any time with <b>? Help</b>.' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taqwright/taqwright",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "description": "E2E mobile testing on the Playwright runner",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -40,6 +40,7 @@
40
40
  "format:check": "prettier --check .",
41
41
  "test": "npm run build && node --test test/*.test.js",
42
42
  "test:watch": "node --test --watch test/*.test.js",
43
+ "test:e2e": "playwright test --config e2e/playwright.config.ts",
43
44
  "test:coverage": "npm run build && node --test --experimental-test-coverage test/*.test.js",
44
45
  "prepare": "npm run build",
45
46
  "prepublishOnly": "npm run build",