@elliemae/pui-app-bridge 2.25.0 → 2.26.0-alpha.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.
Files changed (36) hide show
  1. package/dist/cjs/appBridge.js +173 -26
  2. package/dist/cjs/config/app.js +8 -0
  3. package/dist/cjs/frame.html +0 -21
  4. package/dist/cjs/loaders/script.js +12 -12
  5. package/dist/cjs/tests/serverHandlers.js +0 -8
  6. package/dist/esm/appBridge.js +173 -26
  7. package/dist/esm/config/app.js +8 -0
  8. package/dist/esm/frame.html +0 -21
  9. package/dist/esm/loaders/script.js +12 -12
  10. package/dist/esm/tests/serverHandlers.js +0 -8
  11. package/dist/public/assets/{frame.671d9de68be598da64ca.html → frame.4cbbcfa9ded96b660559.html} +0 -21
  12. package/dist/public/e2e-host.html +1 -1
  13. package/dist/public/e2e-index.html +1 -1
  14. package/dist/public/frame.html +1 -1
  15. package/dist/public/index.html +1 -1
  16. package/dist/public/js/emuiAppBridge.dc4dbeb6feea171656da.js +17 -0
  17. package/dist/public/js/emuiAppBridge.dc4dbeb6feea171656da.js.br +0 -0
  18. package/dist/public/js/emuiAppBridge.dc4dbeb6feea171656da.js.gz +0 -0
  19. package/dist/public/js/emuiAppBridge.dc4dbeb6feea171656da.js.map +1 -0
  20. package/dist/types/lib/appBridge.d.ts +11 -2
  21. package/dist/types/lib/config/app.d.ts +6 -0
  22. package/dist/types/lib/loaders/script.d.ts +5 -3
  23. package/dist/types/lib/typings/host.d.ts +5 -0
  24. package/dist/types/lib/typings/window.d.ts +1 -1
  25. package/dist/types/tsconfig.tsbuildinfo +1 -1
  26. package/dist/umd/{671d9de68be598da64ca.html → 4cbbcfa9ded96b660559.html} +0 -21
  27. package/dist/umd/frame.html +1 -1
  28. package/dist/umd/index.js +7 -7
  29. package/dist/umd/index.js.br +0 -0
  30. package/dist/umd/index.js.gz +0 -0
  31. package/dist/umd/index.js.map +1 -1
  32. package/package.json +5 -5
  33. package/dist/public/js/emuiAppBridge.87b9f6d6c712609094fd.js +0 -17
  34. package/dist/public/js/emuiAppBridge.87b9f6d6c712609094fd.js.br +0 -0
  35. package/dist/public/js/emuiAppBridge.87b9f6d6c712609094fd.js.gz +0 -0
  36. package/dist/public/js/emuiAppBridge.87b9f6d6c712609094fd.js.map +0 -1
@@ -128,18 +128,19 @@ class CAppBridge {
128
128
  };
129
129
  /**
130
130
  * add app to active app list
131
- * @param param0
132
- * @param param0.id
133
- * @param param0.instanceId
134
- * @param elementIds
135
- * @param param0.documentEle
136
- * @param param0.elementIds
131
+ * @param options
132
+ * @param options.id - application id
133
+ * @param options.instanceId - unique instance id
134
+ * @param options.documentEle - iframe document
135
+ * @param options.elementIds - ids of injected script/style elements
136
+ * @param options.microFEHost - pre-created host reference for guest-initiated loading
137
137
  */
138
138
  #addAppToActiveAppList = ({
139
139
  id,
140
140
  instanceId,
141
141
  documentEle,
142
- elementIds
142
+ elementIds,
143
+ microFEHost
143
144
  }) => {
144
145
  const app = this.#appRegistry.get({ id, instanceId });
145
146
  if (!app) {
@@ -152,7 +153,8 @@ class CAppBridge {
152
153
  guest: {
153
154
  guestWindow: documentEle?.defaultView,
154
155
  ...app
155
- }
156
+ },
157
+ microFEHost
156
158
  });
157
159
  };
158
160
  #initApplication = async ({
@@ -164,7 +166,8 @@ class CAppBridge {
164
166
  homeRoute,
165
167
  initialRoute,
166
168
  history,
167
- theme
169
+ theme,
170
+ microFEHost
168
171
  }) => {
169
172
  const app = this.#appRegistry.get({ id, instanceId });
170
173
  if (!app) {
@@ -176,10 +179,8 @@ class CAppBridge {
176
179
  throw new Error(
177
180
  `Application ${id} with instance id ${instanceId} doesn't expose init method`
178
181
  );
179
- const host = new CMicroFEHost({
180
- guest: {
181
- id
182
- },
182
+ const host = microFEHost ?? new CMicroFEHost({
183
+ guest: { id },
183
184
  version: this.#version,
184
185
  containerId,
185
186
  logger: this.#logger,
@@ -207,7 +208,7 @@ class CAppBridge {
207
208
  `Application ${id} with instance id ${instanceId} is loading...`
208
209
  );
209
210
  let assets = files;
210
- const manifest = await ManifestLoader.get(options);
211
+ const manifest = options.manifest ?? await ManifestLoader.get(options);
211
212
  assets = ManifestLoader.getFullFileNameofAssets(manifest, files);
212
213
  const cssAssets = assets.filter((fileName) => isCss(fileName));
213
214
  const jsAssets = assets.filter((fileName) => !isCss(fileName));
@@ -225,9 +226,12 @@ class CAppBridge {
225
226
  id,
226
227
  instanceId,
227
228
  documentEle,
228
- elementIds: [...styleElementIds, ...scriptElementIds]
229
+ elementIds: [...styleElementIds, ...scriptElementIds],
230
+ microFEHost: options.microFEHost
229
231
  });
230
- await this.#initApplication(options);
232
+ if (!options.selfInitialize) {
233
+ await this.#initApplication(options);
234
+ }
231
235
  this.#logger.audit(
232
236
  `Application ${id} with instance id ${instanceId} loaded`
233
237
  );
@@ -326,6 +330,56 @@ class CAppBridge {
326
330
  });
327
331
  }
328
332
  };
333
+ /**
334
+ * If the guest's module SO exposes _setUnloadHandler (duck-typed),
335
+ * bind it to closeApp so the guest can trigger its own teardown.
336
+ * @param id
337
+ * @param instanceId
338
+ */
339
+ #bindModuleUnload = (id, instanceId) => {
340
+ const moduleSO = this.#soManager.getObject(
341
+ ScriptingObjectNames.Module,
342
+ { id }
343
+ );
344
+ const so = moduleSO;
345
+ const handler = so?._setUnloadHandler;
346
+ if (typeof handler === "function") {
347
+ handler(
348
+ () => this.closeApp(instanceId)
349
+ );
350
+ }
351
+ };
352
+ /**
353
+ * Dispatch the module.unloading event and collect feedback.
354
+ * Returns true if unload is allowed, false if any listener vetoed.
355
+ * @param id
356
+ */
357
+ #canUnload = async (id) => {
358
+ const moduleSO = this.#soManager.getObject(
359
+ ScriptingObjectNames.Module,
360
+ { id }
361
+ );
362
+ if (!moduleSO) return true;
363
+ try {
364
+ const feedback = await this.#eventManager.dispatchEvent(moduleSO, {
365
+ event: { id: "module.unloading", name: "unloading" },
366
+ eventParams: { moduleId: id },
367
+ eventOptions: { timeout: 1e3 }
368
+ });
369
+ if (Array.isArray(feedback) && feedback.some((v) => v === false)) {
370
+ this.#logger.info(
371
+ `Unload denied by guest ${id} via module.unloading event`
372
+ );
373
+ return false;
374
+ }
375
+ } catch (err) {
376
+ this.#logger.warn({
377
+ message: `Error dispatching module.unloading event for ${id}`,
378
+ exception: err
379
+ });
380
+ }
381
+ return true;
382
+ };
329
383
  /**
330
384
  * clear session management for the guest application
331
385
  * @param instanceId unique instance id of the application
@@ -458,6 +512,7 @@ class CAppBridge {
458
512
  return;
459
513
  }
460
514
  const { id } = app;
515
+ if (!await this.#canUnload(id)) return;
461
516
  const appConfig = this.#microFEConfig.getConfigById(id);
462
517
  if (!appConfig) {
463
518
  this.#logger.warn(`Configuration for application ${id} is not found`);
@@ -518,14 +573,46 @@ class CAppBridge {
518
573
  */
519
574
  getApps = () => [...this.#activeApps.values()].map((app) => app.guest);
520
575
  /**
521
- * Initialize appBridge
576
+ * Initialize appBridge.
577
+ * @param preloadedAppConfig - Already-parsed config object. When provided
578
+ * the bridge skips its own HTTP fetch of app.config.json.
522
579
  */
523
- init = async () => {
524
- await this.#appConfig.load();
580
+ init = async (preloadedAppConfig) => {
581
+ if (preloadedAppConfig) {
582
+ this.#appConfig.setPreloadedConfig(preloadedAppConfig);
583
+ } else {
584
+ await this.#appConfig.load();
585
+ }
525
586
  this.#microFEConfig.init({
526
587
  version: this.#version,
527
588
  appConfig: this.#appConfig
528
589
  });
590
+ this.#injectPreconnects();
591
+ };
592
+ /**
593
+ * Inject `<link rel="preconnect">` tags into the parent document for each
594
+ * unique micro-app origin. Called once at the end of {@link init} so that
595
+ * DNS/TCP/TLS handshakes start before any iframe is created.
596
+ */
597
+ #injectPreconnects = () => {
598
+ const apps = this.#appConfig.get("microFrontendApps") ?? {};
599
+ const origins = /* @__PURE__ */ new Set();
600
+ Object.keys(apps).forEach((id) => {
601
+ const cfg = this.#microFEConfig.getConfigById(id);
602
+ if (cfg?.hostUrl) {
603
+ try {
604
+ origins.add(new URL(cfg.hostUrl).origin);
605
+ } catch {
606
+ }
607
+ }
608
+ });
609
+ origins.forEach((origin) => {
610
+ const link = document.createElement("link");
611
+ link.rel = "preconnect";
612
+ link.href = origin;
613
+ link.crossOrigin = "anonymous";
614
+ document.head.appendChild(link);
615
+ });
529
616
  };
530
617
  /**
531
618
  * Mount guest micro frontend application into DOM
@@ -546,10 +633,42 @@ class CAppBridge {
546
633
  }
547
634
  await this.#mountApp({ ...appConfig, instanceId });
548
635
  };
636
+ /**
637
+ * Prepare the iframe: create a CMicroFEHost, expose it on window.emui,
638
+ * and inject a preconnect hint for the guest origin.
639
+ * @param frameDoc
640
+ * @param appConfig
641
+ * @param appConfig.hostUrl
642
+ * @param options
643
+ * @param options.id
644
+ * @param options.containerId
645
+ */
646
+ #setupIframe = (frameDoc, appConfig, options) => {
647
+ const microFEHost = new CMicroFEHost({
648
+ guest: { id: options.id },
649
+ version: this.#version,
650
+ containerId: options.containerId,
651
+ logger: this.#logger,
652
+ soManager: this.#soManager,
653
+ eventManager: this.#eventManager
654
+ });
655
+ const iframeWindow = frameDoc.defaultView;
656
+ if (iframeWindow) {
657
+ iframeWindow.emui = iframeWindow.emui ?? {};
658
+ iframeWindow.emui.__host = microFEHost;
659
+ }
660
+ const preconnect = frameDoc.createElement("link");
661
+ preconnect.rel = "preconnect";
662
+ preconnect.href = appConfig.hostUrl;
663
+ preconnect.crossOrigin = "anonymous";
664
+ frameDoc.head.appendChild(preconnect);
665
+ return microFEHost;
666
+ };
549
667
  /**
550
668
  * Open guest micro frontend application
551
669
  * @param {OpenAppParams} params - options to open guest application
552
670
  */
671
+ // eslint-disable-next-line max-statements
553
672
  openApp = async (params) => {
554
673
  const {
555
674
  id,
@@ -558,7 +677,8 @@ class CAppBridge {
558
677
  theme,
559
678
  homeRoute,
560
679
  initialRoute,
561
- metadata
680
+ metadata,
681
+ selfInitialize
562
682
  } = params;
563
683
  const instanceId = uuidv4();
564
684
  if (metadata) this.#guestMetadata.set(id, metadata);
@@ -566,15 +686,15 @@ class CAppBridge {
566
686
  if (!appConfig) {
567
687
  throw new Error(`Application ${id} is not found in app config`);
568
688
  }
689
+ const manifestPromise = ManifestLoader.get(appConfig);
690
+ manifestPromise.catch(() => {
691
+ });
569
692
  const frameEle = await Frame.create({
570
693
  id,
571
694
  instanceId,
572
695
  manifestPath: appConfig.manifestPath,
573
696
  hostUrl: appConfig.hostUrl,
574
- options: {
575
- title: appConfig.name,
576
- ...frameOptions
577
- }
697
+ options: { title: appConfig.name, ...frameOptions }
578
698
  });
579
699
  if (!frameEle?.contentDocument)
580
700
  throw new Error("unable to create iframe for the microapp");
@@ -584,6 +704,15 @@ class CAppBridge {
584
704
  instanceId,
585
705
  documentEle: frameEle.contentDocument
586
706
  });
707
+ const microFEHost = this.#setupIframe(
708
+ frameEle.contentDocument,
709
+ appConfig,
710
+ {
711
+ id,
712
+ containerId: frameOptions?.containerId
713
+ }
714
+ );
715
+ const manifest = await manifestPromise;
587
716
  await this.#loadApp({
588
717
  instanceId,
589
718
  history,
@@ -592,10 +721,16 @@ class CAppBridge {
592
721
  containerId: frameOptions?.containerId,
593
722
  ...appConfig,
594
723
  homeRoute: homeRoute ?? appConfig.homeRoute,
595
- initialRoute
724
+ initialRoute,
725
+ microFEHost,
726
+ selfInitialize,
727
+ manifest
596
728
  });
597
- await this.#mountApp({ instanceId, ...appConfig });
729
+ if (!selfInitialize) {
730
+ await this.#mountApp({ instanceId, ...appConfig });
731
+ }
598
732
  this.#manageSession({ id, instanceId });
733
+ this.#bindModuleUnload(id, instanceId);
599
734
  return instanceId;
600
735
  } catch (err) {
601
736
  this.#unloadApp({
@@ -629,6 +764,18 @@ class CAppBridge {
629
764
  removeScriptingObject = (objectId, guestId) => {
630
765
  this.#soManager.removeScriptingObject(objectId, guestId);
631
766
  };
767
+ /**
768
+ * Pre-fetch the manifest for a guest application so that a subsequent
769
+ * {@link openApp} call can skip the manifest network round-trip.
770
+ * Safe to call multiple times — results are cached.
771
+ * @param id application id as defined in app.config.json
772
+ */
773
+ warmUp = (id) => {
774
+ const appConfig = this.#microFEConfig.getConfigById(id);
775
+ if (!appConfig) return;
776
+ ManifestLoader.get(appConfig).catch(() => {
777
+ });
778
+ };
632
779
  /**
633
780
  * Unmount guest micro frontend application from DOM
634
781
  * @param instanceId unique instance id of guest micro frontend application
@@ -50,6 +50,14 @@ class CAppConfig {
50
50
  * @returns true if key exists
51
51
  */
52
52
  has = (key = "") => lodashHas(this.#gAppConfig, key);
53
+ /**
54
+ * Accept an already-parsed config object, skipping the HTTP fetch.
55
+ * Useful when the caller (e.g. pui-app-sdk) has already loaded the config.
56
+ * @param config
57
+ */
58
+ setPreloadedConfig = (config) => {
59
+ this.#gAppConfig = config;
60
+ };
53
61
  /**
54
62
  * add version to the base url
55
63
  * @returns versioned base url
@@ -6,27 +6,6 @@
6
6
  <meta name="mobile-web-app-capable" content="yes" />
7
7
  <link rel="icon" href="/favicon.ico" />
8
8
  <title>Application</title>
9
- <script nonce="__CSP_NONCE__">
10
- (function (i, s, o, g, r, a, m) {
11
- i['GoogleAnalyticsObject'] = r;
12
- (i[r] =
13
- i[r] ||
14
- function () {
15
- (i[r].q = i[r].q || []).push(arguments);
16
- }),
17
- (i[r].l = 1 * new Date());
18
- (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
19
- a.async = 1;
20
- a.src = g;
21
- m.parentNode.insertBefore(a, m);
22
- })(
23
- window,
24
- document,
25
- 'script',
26
- 'https://www.google-analytics.com/analytics.js',
27
- 'ga',
28
- );
29
- </script>
30
9
  <style nonce="__CSP_NONCE__">
31
10
  .full-width {
32
11
  width: 100%;
@@ -97,9 +97,11 @@ class ScriptLoader {
97
97
  * This method:
98
98
  * - Partitions assets into CDN (HTTP/HTTPS) and non-CDN (relative) arrays in a single pass
99
99
  * - Creates modulepreload links for all assets for performance optimization
100
- * - Loads CDN scripts sequentially first (they may set global variables needed by non-CDN scripts)
101
- * - Then loads non-CDN scripts sequentially
102
- * - Sequential execution ensures proper dependency resolution and initialization order
100
+ * - Appends all script elements to the DOM synchronously so the browser
101
+ * can download them in parallel
102
+ * - Execution order is preserved by the spec: type="module" scripts
103
+ * execute in document order; dynamic async=false scripts execute in
104
+ * insertion order
103
105
  * @example
104
106
  * ```typescript
105
107
  * const scriptIds = await scriptLoader.load(
@@ -134,15 +136,13 @@ class ScriptLoader {
134
136
  nonCdnAssets: []
135
137
  }
136
138
  );
137
- for (const { href, id } of [...cdnAssets, ...nonCdnAssets]) {
138
- await this.#loadScript({
139
- id,
140
- href,
141
- documentEle,
142
- isESMModule
143
- });
144
- }
145
- return [...cdnAssets, ...nonCdnAssets].map((asset) => asset.id);
139
+ const orderedAssets = [...cdnAssets, ...nonCdnAssets];
140
+ await Promise.all(
141
+ orderedAssets.map(
142
+ ({ id, href }) => this.#loadScript({ id, href, documentEle, isESMModule })
143
+ )
144
+ );
145
+ return orderedAssets.map((asset) => asset.id);
146
146
  };
147
147
  /**
148
148
  * Removes a script element from the document by its ID.
@@ -121,14 +121,6 @@ const getServerHandlers = () => {
121
121
  return versionedHandlers;
122
122
  };
123
123
  const serverHandlers = [
124
- rest.get(
125
- "https://www.google-analytics.com/analytics.js",
126
- (req, res, ctx) => res(
127
- ctx.status(200),
128
- ctx.set("Content-Type", "application/javascript"),
129
- ctx.body("")
130
- )
131
- ),
132
124
  rest.get(
133
125
  "/latest/app.config.json",
134
126
  (req, res, ctx) => res(ctx.json(appConfig))
@@ -6,27 +6,6 @@
6
6
  <meta name="mobile-web-app-capable" content="yes" />
7
7
  <link rel="icon" href="/favicon.ico" />
8
8
  <title>Application</title>
9
- <script nonce="__CSP_NONCE__">
10
- (function (i, s, o, g, r, a, m) {
11
- i['GoogleAnalyticsObject'] = r;
12
- (i[r] =
13
- i[r] ||
14
- function () {
15
- (i[r].q = i[r].q || []).push(arguments);
16
- }),
17
- (i[r].l = 1 * new Date());
18
- (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
19
- a.async = 1;
20
- a.src = g;
21
- m.parentNode.insertBefore(a, m);
22
- })(
23
- window,
24
- document,
25
- 'script',
26
- 'https://www.google-analytics.com/analytics.js',
27
- 'ga',
28
- );
29
- </script>
30
9
  <style nonce="__CSP_NONCE__">
31
10
  .full-width {
32
11
  width: 100%;
@@ -1,4 +1,4 @@
1
- <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>AppBridge E2E — Comprehensive Host Test</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiAppBridge.87b9f6d6c712609094fd.js"></script></head><body class="bg-gray-50"><header class="bg-indigo-600 text-white px-4 py-3 flex items-center justify-between"><h1 class="text-lg font-semibold">AppBridge E2E — Comprehensive Host Test</h1><a href="./e2e-index.html" class="text-indigo-200 hover:text-white text-sm">&larr; Back to dashboard</a></header><main class="mx-auto max-w-7xl px-4 py-4"><div class="bg-blue-50 border border-blue-200 rounded-md p-3 mb-4"><h2 class="text-sm font-bold text-blue-900 mb-1">How to Use This Page</h2><ol class="text-xs text-blue-800 list-decimal ml-4 space-y-1"><li>Wait for <strong>"E2E Host ready"</strong> in the Event Log.</li><li>Use buttons in each section to test AppBridge features.</li><li>Check Event Log and result panels for expected output.</li><li>Work through the Verification Checklist at the bottom.</li></ol></div><div class="grid grid-cols-3 gap-4"><div class="col-span-2 space-y-4"><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">App Loading</h2><div class="flex flex-wrap gap-2"><button data-testid="btn-open-app" id="btnOpenApp" class="rounded bg-indigo-600 px-3 py-1 text-xs text-white hover:bg-indigo-700">Open App (pricingservice)</button> <button data-testid="btn-open-credit" id="btnOpenCredit" class="rounded bg-indigo-600 px-3 py-1 text-xs text-white hover:bg-indigo-700">Open App (creditservice)</button> <button data-testid="btn-open-validation" id="btnOpenValidation" class="rounded bg-indigo-600 px-3 py-1 text-xs text-white hover:bg-indigo-700">Open App (loanvalidation)</button> <button data-testid="btn-open-with-metadata" id="btnOpenWithMetadata" class="rounded bg-purple-600 px-3 py-1 text-xs text-white hover:bg-purple-700">Open App with Metadata</button> <button data-testid="btn-open-e2e-guest" id="btnOpenE2EGuest" class="rounded bg-teal-600 px-3 py-1 text-xs text-white hover:bg-teal-700">Open E2E Test Guest</button> <button data-testid="btn-open-nested-chain" id="btnOpenNestedChain" class="rounded bg-orange-600 px-3 py-1 text-xs text-white hover:bg-orange-700">Open Nested Chain (A→B→C)</button></div><p class="text-xs text-gray-400 mt-1"><strong>Expected:</strong> App loads into the container below. Log shows "openApp" succeeded. The E2E Test Guest provides interactive buttons to test guest→host communication. The Nested Chain (A→B→C) opens an intermediate that clones Loan and re-exposes it to a grandchild — use C's buttons to verify callChain propagation on Host A.</p></div><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">Scripting Object Management</h2><div class="flex flex-wrap gap-2"><button data-testid="btn-add-so" id="btnAddSO" class="rounded bg-green-600 px-3 py-1 text-xs text-white hover:bg-green-700">Add "Inventory" Object</button> <button data-testid="btn-remove-so" id="btnRemoveSO" class="rounded bg-red-600 px-3 py-1 text-xs text-white hover:bg-red-700">Remove "Inventory" Object</button> <button data-testid="btn-remove-all-so" id="btnRemoveAllSO" class="rounded bg-red-600 px-3 py-1 text-xs text-white hover:bg-red-700">Remove All Objects</button></div><p class="text-xs text-gray-400 mt-1"><strong>Expected:</strong> Add succeeds. Remove succeeds. Guest apps can/cannot call removed objects.</p></div><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">Event Dispatching</h2><div class="flex flex-wrap gap-2"><button data-testid="btn-dispatch-presave" id="btnDispatchPreSave" class="rounded bg-yellow-600 px-3 py-1 text-xs text-white hover:bg-yellow-700">Dispatch onPreSave (all apps)</button> <button data-testid="btn-dispatch-amount" id="btnDispatchAmount" class="rounded bg-yellow-600 px-3 py-1 text-xs text-white hover:bg-yellow-700">Dispatch onLoanAmountChanged</button> <button data-testid="btn-dispatch-term" id="btnDispatchTerm" class="rounded bg-yellow-600 px-3 py-1 text-xs text-white hover:bg-yellow-700">Dispatch onLoanTermChanged</button></div><div data-testid="dispatch-result" id="dispatchResult" class="mt-2 text-xs bg-gray-100 rounded p-2 min-h-[40px]">Dispatch results appear here...</div></div><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">callContext (from last scripting object invocation)</h2><div class="grid grid-cols-2 gap-2"><div><h3 class="text-xs font-medium text-gray-500 mb-1">callContext.guest</h3><pre data-testid="call-context-guest" id="callContextGuest" class="text-xs bg-gray-100 rounded p-2 min-h-[30px] whitespace-pre-wrap">
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>AppBridge E2E — Comprehensive Host Test</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiAppBridge.dc4dbeb6feea171656da.js"></script></head><body class="bg-gray-50"><header class="bg-indigo-600 text-white px-4 py-3 flex items-center justify-between"><h1 class="text-lg font-semibold">AppBridge E2E — Comprehensive Host Test</h1><a href="./e2e-index.html" class="text-indigo-200 hover:text-white text-sm">&larr; Back to dashboard</a></header><main class="mx-auto max-w-7xl px-4 py-4"><div class="bg-blue-50 border border-blue-200 rounded-md p-3 mb-4"><h2 class="text-sm font-bold text-blue-900 mb-1">How to Use This Page</h2><ol class="text-xs text-blue-800 list-decimal ml-4 space-y-1"><li>Wait for <strong>"E2E Host ready"</strong> in the Event Log.</li><li>Use buttons in each section to test AppBridge features.</li><li>Check Event Log and result panels for expected output.</li><li>Work through the Verification Checklist at the bottom.</li></ol></div><div class="grid grid-cols-3 gap-4"><div class="col-span-2 space-y-4"><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">App Loading</h2><div class="flex flex-wrap gap-2"><button data-testid="btn-open-app" id="btnOpenApp" class="rounded bg-indigo-600 px-3 py-1 text-xs text-white hover:bg-indigo-700">Open App (pricingservice)</button> <button data-testid="btn-open-credit" id="btnOpenCredit" class="rounded bg-indigo-600 px-3 py-1 text-xs text-white hover:bg-indigo-700">Open App (creditservice)</button> <button data-testid="btn-open-validation" id="btnOpenValidation" class="rounded bg-indigo-600 px-3 py-1 text-xs text-white hover:bg-indigo-700">Open App (loanvalidation)</button> <button data-testid="btn-open-with-metadata" id="btnOpenWithMetadata" class="rounded bg-purple-600 px-3 py-1 text-xs text-white hover:bg-purple-700">Open App with Metadata</button> <button data-testid="btn-open-e2e-guest" id="btnOpenE2EGuest" class="rounded bg-teal-600 px-3 py-1 text-xs text-white hover:bg-teal-700">Open E2E Test Guest</button> <button data-testid="btn-open-nested-chain" id="btnOpenNestedChain" class="rounded bg-orange-600 px-3 py-1 text-xs text-white hover:bg-orange-700">Open Nested Chain (A→B→C)</button></div><p class="text-xs text-gray-400 mt-1"><strong>Expected:</strong> App loads into the container below. Log shows "openApp" succeeded. The E2E Test Guest provides interactive buttons to test guest→host communication. The Nested Chain (A→B→C) opens an intermediate that clones Loan and re-exposes it to a grandchild — use C's buttons to verify callChain propagation on Host A.</p></div><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">Scripting Object Management</h2><div class="flex flex-wrap gap-2"><button data-testid="btn-add-so" id="btnAddSO" class="rounded bg-green-600 px-3 py-1 text-xs text-white hover:bg-green-700">Add "Inventory" Object</button> <button data-testid="btn-remove-so" id="btnRemoveSO" class="rounded bg-red-600 px-3 py-1 text-xs text-white hover:bg-red-700">Remove "Inventory" Object</button> <button data-testid="btn-remove-all-so" id="btnRemoveAllSO" class="rounded bg-red-600 px-3 py-1 text-xs text-white hover:bg-red-700">Remove All Objects</button></div><p class="text-xs text-gray-400 mt-1"><strong>Expected:</strong> Add succeeds. Remove succeeds. Guest apps can/cannot call removed objects.</p></div><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">Event Dispatching</h2><div class="flex flex-wrap gap-2"><button data-testid="btn-dispatch-presave" id="btnDispatchPreSave" class="rounded bg-yellow-600 px-3 py-1 text-xs text-white hover:bg-yellow-700">Dispatch onPreSave (all apps)</button> <button data-testid="btn-dispatch-amount" id="btnDispatchAmount" class="rounded bg-yellow-600 px-3 py-1 text-xs text-white hover:bg-yellow-700">Dispatch onLoanAmountChanged</button> <button data-testid="btn-dispatch-term" id="btnDispatchTerm" class="rounded bg-yellow-600 px-3 py-1 text-xs text-white hover:bg-yellow-700">Dispatch onLoanTermChanged</button></div><div data-testid="dispatch-result" id="dispatchResult" class="mt-2 text-xs bg-gray-100 rounded p-2 min-h-[40px]">Dispatch results appear here...</div></div><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">callContext (from last scripting object invocation)</h2><div class="grid grid-cols-2 gap-2"><div><h3 class="text-xs font-medium text-gray-500 mb-1">callContext.guest</h3><pre data-testid="call-context-guest" id="callContextGuest" class="text-xs bg-gray-100 rounded p-2 min-h-[30px] whitespace-pre-wrap">
2
2
  Waiting for invocation...</pre></div><div><h3 class="text-xs font-medium text-gray-500 mb-1">callContext.callChain</h3><pre data-testid="call-context-chain" id="callContextChain" class="text-xs bg-gray-100 rounded p-2 min-h-[30px] whitespace-pre-wrap">
3
3
  Waiting for invocation...</pre></div></div></div><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">App Lifecycle</h2><div class="flex flex-wrap gap-2"><button data-testid="btn-close-app" id="btnCloseApp" class="rounded bg-red-600 px-3 py-1 text-xs text-white hover:bg-red-700">Close First App</button> <button data-testid="btn-close-all" id="btnCloseAll" class="rounded bg-red-800 px-3 py-1 text-xs text-white hover:bg-red-900">Close All Apps</button> <button data-testid="btn-list-apps" id="btnListApps" class="rounded bg-gray-600 px-3 py-1 text-xs text-white hover:bg-gray-700">List Apps</button></div><pre data-testid="app-list" id="appList" class="mt-2 text-xs bg-gray-100 rounded p-2 min-h-[30px] whitespace-pre-wrap">
4
4
  (no apps)</pre></div></div><div class="space-y-4"><div class="bg-white rounded-lg shadow p-3"><h2 class="text-sm font-semibold text-gray-700 mb-2">Guest App Container</h2><div data-testid="app-container" id="appContainer" class="border-2 border-dashed border-indigo-300 rounded-lg" style="height:700px;overflow:auto"></div></div><div class="bg-white rounded-lg shadow p-3"><div class="flex justify-between items-center mb-2"><h2 class="text-sm font-semibold text-gray-700">Event Log</h2><button data-testid="btn-clear-log" id="btnClearLog" class="rounded bg-gray-200 px-2 py-0.5 text-xs text-gray-600 hover:bg-gray-300">Clear</button></div><div data-testid="event-log" id="eventLog" class="text-xs bg-gray-100 rounded p-2 min-h-[200px] max-h-[400px] overflow-y-auto"></div></div></div></div><div class="bg-gray-100 border border-gray-300 rounded-md p-3 mt-4"><h2 class="text-sm font-bold text-gray-800 mb-2">Verification Checklist</h2><div class="grid grid-cols-2 gap-2 text-xs"><label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-APP-01:</strong> openApp loads pricingservice into container, log shows mounted</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-APP-02:</strong> openApp loads creditservice into container</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-APP-03:</strong> openApp loads loanvalidation into container</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-APP-04:</strong> openApp with metadata — metadata stored for callChain</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SO-01:</strong> Loan object registered on init (guest apps can call getLoanDetails)</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SO-02:</strong> Add Inventory object succeeds</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SO-03:</strong> Remove Inventory object succeeds</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SO-04:</strong> removeAllScriptingObjects clears everything</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SO-05:</strong> callContext.guest populated when guest invokes scripting object method</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-SO-06:</strong> Loan.getLoanDetails returns expected loan data</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-EVT-01:</strong> Dispatch onPreSave reaches all subscribed apps, results returned</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-EVT-02:</strong> Dispatch onLoanAmountChanged reaches subscribed apps</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-EVT-03:</strong> Dispatch onLoanTermChanged reaches subscribed apps</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LIFE-01:</strong> closeApp removes first app from list</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LIFE-02:</strong> List Apps shows all active apps with instanceIds</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LIFE-03:</strong> closeAllApps removes all apps</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-LIFE-04:</strong> No console errors during any operation</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-META-01:</strong> openApp with metadata — callContext.callChain contains metadata when guest invokes cloned object</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-GUEST-01:</strong> Guest getObject("Loan") returns proxy with expected methods (getLoanDetails, setCreditScore)</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-GUEST-02:</strong> Guest Loan.getLoanDetails() returns loan data, host callContext.guest shows e2etestguest</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-GUEST-03:</strong> Guest Loan.setCreditScore(800) returns {updated: true}, host callContext updates</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-GUEST-04:</strong> Guest getObject("Inventory") returns proxy after host adds it; null before</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-GUEST-05:</strong> Guest Inventory.getItems() returns item array</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-GUEST-06:</strong> Guest getObject("NonExistent") returns null</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-GUEST-07:</strong> Guest subscribes to onPreSave — receives event when host dispatches</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-GUEST-08:</strong> Guest subscribes to onLoanAmountChanged — receives event when host dispatches</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-GUEST-09:</strong> Guest unsubscribe all — no more events received after unsubscribe</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CHAIN-01:</strong> Open Nested Chain — Intermediate B loads, shows "CAppBridge B initialized"</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CHAIN-02:</strong> Grandchild C (E2E Test Guest) appears inside Intermediate B</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CHAIN-03:</strong> In C: getObject("Loan") → Loan.getLoanDetails() → Host A shows callContext.guest = e2eintermediateguest, callChain = [{id: "e2etestguest", metadata: {role: "grandchild", ...}}]</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-CHAIN-04:</strong> In C: Loan.setCreditScore(800) → Host A callChain includes e2etestguest with metadata from B</span></label></div></div></main><script src="./e2e-host.js" type="module"></script></body></html>
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>AppBridge E2E Test Suite</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script defer="defer" src="js/emuiAppBridge.87b9f6d6c712609094fd.js"></script></head><body class="bg-gray-50"><header class="bg-gray-900 text-white px-6 py-4"><h1 class="text-xl font-bold">AppBridge End-to-End Test Suite</h1><p class="text-sm text-gray-400 mt-1">Comprehensive blackbox tests for AppBridge (in-process micro-frontend framework). Each page includes test steps, expected values, and a verification checklist.</p></header><main class="mx-auto max-w-5xl px-6 py-6"><section class="mb-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4"><h2 class="text-sm font-semibold text-yellow-800 mb-2">Setup Instructions</h2><ol class="text-xs text-yellow-700 list-decimal ml-4 space-y-1"><li>Install dependencies: <code class="bg-yellow-100 px-1 rounded">pnpm install</code></li><li>Start AppBridge dev server: <code class="bg-yellow-100 px-1 rounded">cd libs/app-bridge && pnpm start</code> (default port 3000)</li><li>Open <code class="bg-yellow-100 px-1 rounded">http://localhost:3000/e2e-index.html</code> in the browser</li><li>Each test page has <code class="bg-yellow-100 px-1 rounded">data-testid</code> attributes for Playwright/Cypress selectors</li></ol></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">1. Core AppBridge Host Tests</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./e2e-host.html" data-testid="link-e2e-host" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Comprehensive E2E Host</h3><p class="text-xs text-gray-500 mt-1">All host scenarios: openApp, closeApp, scripting objects, events, lifecycle, metadata, callContext.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Apps open and mount into containers. Scripting objects can be added/removed/invoked with correct callContext. Events reach subscribed apps. closeApp/closeAllApps cleans up properly.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-APP-01..04 &bull; TC-SO-01..06 &bull; TC-EVT-01..03 &bull; TC-LIFE-01..04 &bull; TC-META-01</div></a><a href="./index.html" data-testid="link-main-demo" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Main Demo App</h3><p class="text-xs text-gray-500 mt-1">Original Loan Application demo with credit, pricing, and loan validation micro-apps.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Loan form data flows to guest apps. Events (onLoanAmountChanged, etc.) update guest UIs. Credit score updates from guest. Save dispatches onPreSave with feedback.</p></a></div></section></main></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>AppBridge E2E Test Suite</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script defer="defer" src="js/emuiAppBridge.dc4dbeb6feea171656da.js"></script></head><body class="bg-gray-50"><header class="bg-gray-900 text-white px-6 py-4"><h1 class="text-xl font-bold">AppBridge End-to-End Test Suite</h1><p class="text-sm text-gray-400 mt-1">Comprehensive blackbox tests for AppBridge (in-process micro-frontend framework). Each page includes test steps, expected values, and a verification checklist.</p></header><main class="mx-auto max-w-5xl px-6 py-6"><section class="mb-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4"><h2 class="text-sm font-semibold text-yellow-800 mb-2">Setup Instructions</h2><ol class="text-xs text-yellow-700 list-decimal ml-4 space-y-1"><li>Install dependencies: <code class="bg-yellow-100 px-1 rounded">pnpm install</code></li><li>Start AppBridge dev server: <code class="bg-yellow-100 px-1 rounded">cd libs/app-bridge && pnpm start</code> (default port 3000)</li><li>Open <code class="bg-yellow-100 px-1 rounded">http://localhost:3000/e2e-index.html</code> in the browser</li><li>Each test page has <code class="bg-yellow-100 px-1 rounded">data-testid</code> attributes for Playwright/Cypress selectors</li></ol></section><section class="mb-6"><h2 class="text-lg font-semibold text-gray-800 border-b pb-1 mb-3">1. Core AppBridge Host Tests</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-3"><a href="./e2e-host.html" data-testid="link-e2e-host" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Comprehensive E2E Host</h3><p class="text-xs text-gray-500 mt-1">All host scenarios: openApp, closeApp, scripting objects, events, lifecycle, metadata, callContext.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Apps open and mount into containers. Scripting objects can be added/removed/invoked with correct callContext. Events reach subscribed apps. closeApp/closeAllApps cleans up properly.</p><div class="mt-2 text-xs text-gray-400"><strong>Test Cases:</strong> TC-APP-01..04 &bull; TC-SO-01..06 &bull; TC-EVT-01..03 &bull; TC-LIFE-01..04 &bull; TC-META-01</div></a><a href="./index.html" data-testid="link-main-demo" class="block bg-white rounded-lg shadow p-4 hover:shadow-md transition"><h3 class="font-medium text-indigo-700">Main Demo App</h3><p class="text-xs text-gray-500 mt-1">Original Loan Application demo with credit, pricing, and loan validation micro-apps.</p><p class="text-xs text-gray-400 mt-2"><strong>What to verify:</strong> Loan form data flows to guest apps. Events (onLoanAmountChanged, etc.) update guest UIs. Credit score updates from guest. Save dispatches onPreSave with feedback.</p></a></div></section></main></body></html>
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="mobile-web-app-capable" content="yes"/><link rel="icon" href="/favicon.ico"/><title>Application</title><script nonce="__CSP_NONCE__">!function(e,t,n,a,c,o,s){e.GoogleAnalyticsObject=c,e[c]=e[c]||function(){(e[c].q=e[c].q||[]).push(arguments)},e[c].l=1*new Date,o=t.createElement(n),s=t.getElementsByTagName(n)[0],o.async=1,o.src="https://www.google-analytics.com/analytics.js",s.parentNode.insertBefore(o,s)}(window,document,"script",0,"ga")</script><style nonce="__CSP_NONCE__">.full-width{width:100%}.full-height{height:100%}</style><script defer="defer" src="js/emuiAppBridge.87b9f6d6c712609094fd.js"></script></head><body class="full-width full-height"><noscript>If you're seeing this message, that means <strong>JavaScript has been disabled on your browser</strong>, please <strong>enable JS</strong> to make this app work.</noscript><div id="pui-app-container-" class="full-width full-height"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="mobile-web-app-capable" content="yes"/><link rel="icon" href="/favicon.ico"/><title>Application</title><style nonce="__CSP_NONCE__">.full-width{width:100%}.full-height{height:100%}</style><script defer="defer" src="js/emuiAppBridge.dc4dbeb6feea171656da.js"></script></head><body class="full-width full-height"><noscript>If you're seeing this message, that means <strong>JavaScript has been disabled on your browser</strong>, please <strong>enable JS</strong> to make this app work.</noscript><div id="pui-app-container-" class="full-width full-height"></div></body></html>
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Host</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiAppBridge.87b9f6d6c712609094fd.js"></script></head><body><header class="bg-indigo-300 h-10 flex place-items-center justify-between"><div class="px-2">ICE Mortgage Product</div><nav class="flex gap-3 px-2 text-sm"><a href="./e2e-index.html" class="text-indigo-800 hover:text-indigo-950 font-medium">E2E Test Suite</a> <a href="./e2e-host.html" class="text-indigo-800 hover:text-indigo-950 font-medium">E2E Host</a></nav></header><main class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8"><div class="min-w-0 flex-1 mt-4"><h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">Loan Application</h1></div><div id="successFeedback" class="hidden rounded-md bg-green-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"/></svg></div><div class="ml-3"><p class="text-sm font-medium text-green-800">Loan Saved Successfully</p></div></div></div><div id="errorFeedback" class="hidden rounded-md bg-red-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd"/></svg></div><div class="ml-3"><h3 class="text-sm font-medium text-red-800">Credit Score is not meeting the requirement</h3></div></div></div><div class="mt-2 sm:grid sm:grid-cols-2 sm:gap-2"><form class="px-2 py-2 space-y-8 divide-y divide-gray-200 bg-gray-50"><div class="space-y-8 divide-y divide-gray-200 sm:space-y-5"><div class="space-y-6 sm:space-y-5"><div><h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="firstName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">First name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="firstName" id="firstName" autocomplete="given-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="John" placeholder="John"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="lastName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Last name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="lastName" id="lastName" autocomplete="family-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="Doe" placeholder="Doe"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="ssn" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">SSN</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="ssn" id="ssn" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="123456789" placeholder="123456789"/></div></div></div><div><h3 class="text-lg font-medium leading-6 text-gray-900">Loan Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="amount" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Amount</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="amount" id="amount" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="500000" placeholder="500000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="Term" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Term (years)</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="term" id="term" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="30" placeholder="30"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="downPayment" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Down Payment</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="downPayment" id="downPayment" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="50000" placeholder="50000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="creditScore" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Credit Score</label><div class="mt-1 sm:mt-0"><output id="creditScore" class="block w-full max-w-lg pl-2 pt-2 sm:max-w-xs sm:text-sm">NA</output></div><div class="mt-1 sm:mt-0"><button id="getCreditScore" type="button" class="inline-flex items-center rounded-full border border-transparent bg-indigo-600 p-1 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/></svg></button></div></div></div></div></div><button id="saveLoan" type="button" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save</button></form><div id="aside-container" class="flex flex-col gap-4 items-start mt-4 border-2 p-2 rounded-lg border-dashed border-cyan-300 sm:mt-0"></div></div><div id="bottom-container" class="flex flex-col gap-4 items-start mt-4 p-2 sm:mt-0"></div></main><script src="./init.js" type="module"></script></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Host</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiAppBridge.dc4dbeb6feea171656da.js"></script></head><body><header class="bg-indigo-300 h-10 flex place-items-center justify-between"><div class="px-2">ICE Mortgage Product</div><nav class="flex gap-3 px-2 text-sm"><a href="./e2e-index.html" class="text-indigo-800 hover:text-indigo-950 font-medium">E2E Test Suite</a> <a href="./e2e-host.html" class="text-indigo-800 hover:text-indigo-950 font-medium">E2E Host</a></nav></header><main class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8"><div class="min-w-0 flex-1 mt-4"><h1 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">Loan Application</h1></div><div id="successFeedback" class="hidden rounded-md bg-green-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"/></svg></div><div class="ml-3"><p class="text-sm font-medium text-green-800">Loan Saved Successfully</p></div></div></div><div id="errorFeedback" class="hidden rounded-md bg-red-50 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd"/></svg></div><div class="ml-3"><h3 class="text-sm font-medium text-red-800">Credit Score is not meeting the requirement</h3></div></div></div><div class="mt-2 sm:grid sm:grid-cols-2 sm:gap-2"><form class="px-2 py-2 space-y-8 divide-y divide-gray-200 bg-gray-50"><div class="space-y-8 divide-y divide-gray-200 sm:space-y-5"><div class="space-y-6 sm:space-y-5"><div><h3 class="text-lg font-medium leading-6 text-gray-900">Personal Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="firstName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">First name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="firstName" id="firstName" autocomplete="given-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="John" placeholder="John"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="lastName" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Last name</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input name="lastName" id="lastName" autocomplete="family-name" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="Doe" placeholder="Doe"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="ssn" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">SSN</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="ssn" id="ssn" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="123456789" placeholder="123456789"/></div></div></div><div><h3 class="text-lg font-medium leading-6 text-gray-900">Loan Information</h3></div><div class="space-y-6 sm:space-y-5"><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="amount" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Amount</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="amount" id="amount" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="500000" placeholder="500000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="Term" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Term (years)</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="term" id="term" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="30" placeholder="30"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="downPayment" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Down Payment</label><div class="mt-1 sm:col-span-2 sm:mt-0"><input type="number" name="downPayment" id="downPayment" class="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:max-w-xs sm:text-sm" value="50000" placeholder="50000"/></div></div><div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-gray-200"><label for="creditScore" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">Credit Score</label><div class="mt-1 sm:mt-0"><output id="creditScore" class="block w-full max-w-lg pl-2 pt-2 sm:max-w-xs sm:text-sm">NA</output></div><div class="mt-1 sm:mt-0"><button id="getCreditScore" type="button" class="inline-flex items-center rounded-full border border-transparent bg-indigo-600 p-1 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/></svg></button></div></div></div></div></div><button id="saveLoan" type="button" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save</button></form><div id="aside-container" class="flex flex-col gap-4 items-start mt-4 border-2 p-2 rounded-lg border-dashed border-cyan-300 sm:mt-0"></div></div><div id="bottom-container" class="flex flex-col gap-4 items-start mt-4 p-2 sm:mt-0"></div></main><script src="./init.js" type="module"></script></body></html>