@bigbinary/neeto-playwright-commons 3.0.8 → 3.1.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.
package/README.md CHANGED
@@ -132,6 +132,7 @@ import { COMMON_SELECTORS } from "@bigbinary/neeto-playwright-common";
132
132
  - [MailerUtils](./docs/utils/mailer-utils.md)
133
133
  - [EmailDeliveryUtils](./docs/utils/email-delivery-utils.md)
134
134
  - [NeetoAuthServer](./docs/utils/neeto-auth-server.md)
135
+ - [NeetoChatWidget](./docs/utils/neeto-chat-widget.md)
135
136
  - [Util functions](./docs/utils)
136
137
  - [POMs](./docs/poms)
137
138
  - [Integration](./docs/integration)
package/index.cjs.js CHANGED
@@ -125124,22 +125124,22 @@ async function warmup({ urls = DEFAULT_WARMUP_URLS, timeout = 60_000, } = {}) {
125124
125124
  }
125125
125125
  }
125126
125126
 
125127
- const CONFIG = {
125127
+ const CONFIG$1 = {
125128
125128
  DIR: "/tmp/neeto-auth-web",
125129
125129
  LOG: "/tmp/neeto-auth-server.log",
125130
125130
  PORT: 9000,
125131
125131
  MAX_WAIT_MS: 120_000,
125132
125132
  EMAIL_INIT: "/tmp/neeto-auth-web/config/initializers/playwright_email_capture.rb",
125133
125133
  };
125134
- const SCRIPTS_DIR = path__namespace.join(__dirname, "scripts", "neeto-auth");
125134
+ const SCRIPTS_DIR$1 = path__namespace.join(__dirname, "scripts", "neeto-auth");
125135
125135
  const escapeRubyString = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
125136
125136
  const buildEmailCaptureInitializer = (targetApp) => {
125137
- const template = fs__namespace.readFileSync(path__namespace.join(SCRIPTS_DIR, "playwright_email_capture.rb.template"), "utf-8");
125137
+ const template = fs__namespace.readFileSync(path__namespace.join(SCRIPTS_DIR$1, "playwright_email_capture.rb.template"), "utf-8");
125138
125138
  return template.replace("{{TARGET_APP}}", escapeRubyString(targetApp));
125139
125139
  };
125140
- const log = (msg) => console.log(`[NeetoAuth] ${msg}`);
125141
- const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
125142
- const httpCheck = (port, timeout = 3000) => new Promise(resolve => {
125140
+ const log$1 = (msg) => console.log(`[NeetoAuth] ${msg}`);
125141
+ const sleep$1 = (ms) => new Promise(resolve => setTimeout(resolve, ms));
125142
+ const httpCheck$1 = (port, timeout = 3000) => new Promise(resolve => {
125143
125143
  const req = http__namespace.get(`http://localhost:${port}/`, () => resolve(true));
125144
125144
  req.on("error", () => resolve(false));
125145
125145
  req.setTimeout(timeout, () => {
@@ -125166,7 +125166,7 @@ class NeetoAuthServer {
125166
125166
  while (Date.now() < deadline) {
125167
125167
  try {
125168
125168
  process.kill(pid, 0);
125169
- await sleep(500);
125169
+ await sleep$1(500);
125170
125170
  }
125171
125171
  catch {
125172
125172
  return;
@@ -125175,27 +125175,27 @@ class NeetoAuthServer {
125175
125175
  };
125176
125176
  killServerOnPort = async () => {
125177
125177
  try {
125178
- child_process.execSync(`lsof -ti tcp:${CONFIG.PORT} | xargs kill -9`, {
125178
+ child_process.execSync(`lsof -ti tcp:${CONFIG$1.PORT} | xargs kill -9`, {
125179
125179
  stdio: "ignore",
125180
125180
  });
125181
- await sleep(1000);
125181
+ await sleep$1(1000);
125182
125182
  }
125183
125183
  catch {
125184
125184
  // No process on that port — nothing to kill.
125185
125185
  }
125186
125186
  };
125187
125187
  ensureSetup = () => {
125188
- if (fs__namespace.existsSync(`${CONFIG.DIR}/Gemfile`)) {
125189
- log("Already set up, skipping.");
125188
+ if (fs__namespace.existsSync(`${CONFIG$1.DIR}/Gemfile`)) {
125189
+ log$1("Already set up, skipping.");
125190
125190
  return;
125191
125191
  }
125192
125192
  if (!process.env.GITHUB_TOKEN) {
125193
125193
  throw new Error("[NeetoAuth] GITHUB_TOKEN is not set. Cannot clone neeto-auth-web.");
125194
125194
  }
125195
- log("Running first-time setup...");
125196
- const setupScript = path__namespace.join(SCRIPTS_DIR, "setup.sh");
125195
+ log$1("Running first-time setup...");
125196
+ const setupScript = path__namespace.join(SCRIPTS_DIR$1, "setup.sh");
125197
125197
  try {
125198
- child_process.execSync(`bash "${setupScript}" "${CONFIG.DIR}"`, {
125198
+ child_process.execSync(`bash "${setupScript}" "${CONFIG$1.DIR}"`, {
125199
125199
  stdio: "inherit",
125200
125200
  env: this.serverEnv,
125201
125201
  });
@@ -125203,73 +125203,73 @@ class NeetoAuthServer {
125203
125203
  catch {
125204
125204
  throw new Error("[NeetoAuth] First-time setup failed. Check the output above for details.");
125205
125205
  }
125206
- log("Setup complete.");
125206
+ log$1("Setup complete.");
125207
125207
  };
125208
- waitForServer = async (timeoutMs = CONFIG.MAX_WAIT_MS) => {
125208
+ waitForServer = async (timeoutMs = CONFIG$1.MAX_WAIT_MS) => {
125209
125209
  const startTime = Date.now();
125210
125210
  while (Date.now() - startTime < timeoutMs) {
125211
125211
  if (this.process !== null && this.process.exitCode !== null) {
125212
- const logContent = fs__namespace.existsSync(CONFIG.LOG)
125213
- ? fs__namespace.readFileSync(CONFIG.LOG, "utf-8")
125212
+ const logContent = fs__namespace.existsSync(CONFIG$1.LOG)
125213
+ ? fs__namespace.readFileSync(CONFIG$1.LOG, "utf-8")
125214
125214
  : "<no log file>";
125215
125215
  throw new Error(`[NeetoAuth] Server process exited unexpectedly with code ${this.process.exitCode}.\nLog:\n${logContent}`);
125216
125216
  }
125217
- if (await httpCheck(CONFIG.PORT))
125217
+ if (await httpCheck$1(CONFIG$1.PORT))
125218
125218
  return;
125219
- await sleep(3000);
125219
+ await sleep$1(3000);
125220
125220
  }
125221
- const logContent = fs__namespace.existsSync(CONFIG.LOG)
125222
- ? fs__namespace.readFileSync(CONFIG.LOG, "utf-8")
125221
+ const logContent = fs__namespace.existsSync(CONFIG$1.LOG)
125222
+ ? fs__namespace.readFileSync(CONFIG$1.LOG, "utf-8")
125223
125223
  : "<no log file>";
125224
125224
  throw new Error(`[NeetoAuth] Server did not start within ${timeoutMs / 1000}s.\nLog:\n${logContent}`);
125225
125225
  };
125226
125226
  injectEmailCaptureInitializer = () => {
125227
- fs__namespace.writeFileSync(CONFIG.EMAIL_INIT, buildEmailCaptureInitializer(this.targetApp));
125227
+ fs__namespace.writeFileSync(CONFIG$1.EMAIL_INIT, buildEmailCaptureInitializer(this.targetApp));
125228
125228
  };
125229
125229
  isRunningForCurrentApp = () => {
125230
- if (!fs__namespace.existsSync(CONFIG.EMAIL_INIT))
125230
+ if (!fs__namespace.existsSync(CONFIG$1.EMAIL_INIT))
125231
125231
  return false;
125232
- const currentContent = fs__namespace.readFileSync(CONFIG.EMAIL_INIT, "utf-8");
125232
+ const currentContent = fs__namespace.readFileSync(CONFIG$1.EMAIL_INIT, "utf-8");
125233
125233
  return currentContent === buildEmailCaptureInitializer(this.targetApp);
125234
125234
  };
125235
125235
  start = async () => {
125236
125236
  if (IS_STAGING_ENV)
125237
125237
  return;
125238
- if (await httpCheck(CONFIG.PORT)) {
125238
+ if (await httpCheck$1(CONFIG$1.PORT)) {
125239
125239
  if (this.isRunningForCurrentApp()) {
125240
- log("Server already running for this app.");
125240
+ log$1("Server already running for this app.");
125241
125241
  return;
125242
125242
  }
125243
- log("Server running for a different app — restarting...");
125243
+ log$1("Server running for a different app — restarting...");
125244
125244
  await this.killServerOnPort();
125245
125245
  }
125246
125246
  this.ensureSetup();
125247
125247
  this.injectEmailCaptureInitializer();
125248
- log("Starting server...");
125249
- const logStream = fs__namespace.createWriteStream(CONFIG.LOG);
125248
+ log$1("Starting server...");
125249
+ const logStream = fs__namespace.createWriteStream(CONFIG$1.LOG);
125250
125250
  this.process = child_process.spawn("bundle", [
125251
125251
  "exec",
125252
125252
  "rails",
125253
125253
  "server",
125254
125254
  "-p",
125255
- CONFIG.PORT.toString(),
125255
+ CONFIG$1.PORT.toString(),
125256
125256
  "-b",
125257
125257
  "0.0.0.0",
125258
125258
  ], {
125259
- cwd: CONFIG.DIR,
125259
+ cwd: CONFIG$1.DIR,
125260
125260
  stdio: ["ignore", "pipe", "pipe"],
125261
125261
  env: this.serverEnv,
125262
125262
  });
125263
125263
  this.process.stdout?.pipe(logStream);
125264
125264
  this.process.stderr?.pipe(logStream);
125265
125265
  await this.waitForServer();
125266
- log("Server ready.");
125266
+ log$1("Server ready.");
125267
125267
  };
125268
125268
  stop = async () => {
125269
125269
  if (IS_STAGING_ENV || !this.process)
125270
125270
  return;
125271
125271
  const pid = this.process.pid;
125272
- log("Stopping server...");
125272
+ log$1("Stopping server...");
125273
125273
  this.process.kill("SIGTERM");
125274
125274
  this.process = null;
125275
125275
  if (pid) {
@@ -125278,6 +125278,238 @@ class NeetoAuthServer {
125278
125278
  };
125279
125279
  }
125280
125280
 
125281
+ const CONFIG = {
125282
+ DIR: "/tmp/neeto-chat-widget",
125283
+ LOG: "/tmp/neeto-chat-widget.log",
125284
+ PORT: 8081,
125285
+ MAX_WAIT_MS: 180_000,
125286
+ LOCK_RETRY_MS: 500,
125287
+ SERVER_POLL_INTERVAL_MS: 3000,
125288
+ WEBPACK_DEV_FILE: "/tmp/neeto-chat-widget/webpack.dev.js",
125289
+ LOCK_DIR: "/tmp/neeto-chat-widget.lock",
125290
+ STATE_FILE: "/tmp/neeto-chat-widget.state.json",
125291
+ };
125292
+ const DEFAULT_STATE = { pid: null, refCount: 0 };
125293
+ const WIDGET_BASE_URL = `http://localhost:${CONFIG.PORT}`;
125294
+ const SCRIPTS_DIR = path__namespace.join(__dirname, "scripts", "neeto-chat-widget");
125295
+ const log = (msg) => console.log(`[NeetoChatWidget] ${msg}`);
125296
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
125297
+ const httpCheck = (timeout = 3000) => new Promise(resolve => {
125298
+ const req = http__namespace.get(`${WIDGET_BASE_URL}/main.js`, () => resolve(true));
125299
+ req.on("error", () => resolve(false));
125300
+ req.setTimeout(timeout, () => {
125301
+ req.destroy();
125302
+ resolve(false);
125303
+ });
125304
+ });
125305
+ class NeetoChatWidget {
125306
+ process = null;
125307
+ hasLease = false;
125308
+ logStream = null;
125309
+ get serverEnv() {
125310
+ return { ...process.env };
125311
+ }
125312
+ withLock = async (fn) => {
125313
+ while (true) {
125314
+ try {
125315
+ fs$4.mkdirSync(CONFIG.LOCK_DIR);
125316
+ break;
125317
+ }
125318
+ catch (error) {
125319
+ const nodeError = error;
125320
+ if (nodeError.code !== "EEXIST")
125321
+ throw error;
125322
+ await sleep(CONFIG.LOCK_RETRY_MS);
125323
+ }
125324
+ }
125325
+ try {
125326
+ return await Promise.resolve(fn());
125327
+ }
125328
+ finally {
125329
+ fs$4.rmSync(CONFIG.LOCK_DIR, { force: true, recursive: true });
125330
+ }
125331
+ };
125332
+ parseState = (raw) => {
125333
+ if (!raw || typeof raw !== "object")
125334
+ return DEFAULT_STATE;
125335
+ const state = raw;
125336
+ const pid = typeof state.pid === "number" ? state.pid : null;
125337
+ const refCount = typeof state.refCount === "number" && state.refCount > 0
125338
+ ? state.refCount
125339
+ : 0;
125340
+ return { pid, refCount };
125341
+ };
125342
+ readState = () => {
125343
+ if (!fs$4.existsSync(CONFIG.STATE_FILE))
125344
+ return DEFAULT_STATE;
125345
+ try {
125346
+ return this.parseState(JSON.parse(fs$4.readFileSync(CONFIG.STATE_FILE, "utf-8")));
125347
+ }
125348
+ catch {
125349
+ return DEFAULT_STATE;
125350
+ }
125351
+ };
125352
+ writeState = (state) => {
125353
+ fs$4.writeFileSync(CONFIG.STATE_FILE, JSON.stringify(state));
125354
+ };
125355
+ clearState = () => {
125356
+ fs$4.existsSync(CONFIG.STATE_FILE) && fs$4.rmSync(CONFIG.STATE_FILE, { force: true });
125357
+ };
125358
+ getLogContent = () => fs$4.existsSync(CONFIG.LOG)
125359
+ ? fs$4.readFileSync(CONFIG.LOG, "utf-8")
125360
+ : "<no log file>";
125361
+ ensureSetup = () => {
125362
+ if (fs$4.existsSync(CONFIG.WEBPACK_DEV_FILE)) {
125363
+ this.patchWebpackPublicPath();
125364
+ log("Already set up, skipping.");
125365
+ return;
125366
+ }
125367
+ fs$4.existsSync(CONFIG.DIR) &&
125368
+ fs$4.rmSync(CONFIG.DIR, { force: true, recursive: true });
125369
+ const setupScript = path__namespace.join(SCRIPTS_DIR, "setup.sh");
125370
+ log("Running first-time setup...");
125371
+ try {
125372
+ child_process.execSync(`bash "${setupScript}" "${CONFIG.DIR}"`, {
125373
+ stdio: "inherit",
125374
+ env: this.serverEnv,
125375
+ });
125376
+ }
125377
+ catch {
125378
+ throw new Error("[NeetoChatWidget] First-time setup failed. Check the output above for details.");
125379
+ }
125380
+ this.patchWebpackPublicPath();
125381
+ log("Setup complete.");
125382
+ };
125383
+ patchWebpackPublicPath = () => {
125384
+ if (!fs$4.existsSync(CONFIG.WEBPACK_DEV_FILE)) {
125385
+ throw new Error(`[NeetoChatWidget] Missing webpack config at ${CONFIG.WEBPACK_DEV_FILE}`);
125386
+ }
125387
+ const fileContent = fs$4.readFileSync(CONFIG.WEBPACK_DEV_FILE, "utf-8");
125388
+ const targetPublicPath = `publicPath: "${WIDGET_BASE_URL}/"`;
125389
+ if (fileContent.includes(targetPublicPath))
125390
+ return;
125391
+ const oldConfig = ` devServer: {\n contentBase: "./src",\n allowedHosts: [".lvh.me"],\n },\n`;
125392
+ const newConfig = ` devServer: {\n contentBase: "./src",\n allowedHosts: [".lvh.me"],\n publicPath: "${WIDGET_BASE_URL}/",\n },\n output: {\n publicPath: "${WIDGET_BASE_URL}/",\n },\n`;
125393
+ if (!fileContent.includes(oldConfig)) {
125394
+ throw new Error("[NeetoChatWidget] Unable to patch webpack.dev.js for local widget serving.");
125395
+ }
125396
+ fs$4.writeFileSync(CONFIG.WEBPACK_DEV_FILE, fileContent.replace(oldConfig, newConfig));
125397
+ };
125398
+ closeLogStream = () => {
125399
+ if (this.logStream) {
125400
+ this.logStream.end();
125401
+ this.logStream = null;
125402
+ }
125403
+ };
125404
+ waitForProcessExit = async (pid) => {
125405
+ const deadline = Date.now() + 10_000;
125406
+ while (Date.now() < deadline) {
125407
+ try {
125408
+ process.kill(pid, 0);
125409
+ await sleep(500);
125410
+ }
125411
+ catch {
125412
+ return;
125413
+ }
125414
+ }
125415
+ };
125416
+ killServer = (pid) => {
125417
+ if (!pid)
125418
+ return;
125419
+ try {
125420
+ process.kill(pid, "SIGTERM");
125421
+ }
125422
+ catch {
125423
+ // process may already be gone
125424
+ }
125425
+ };
125426
+ killProcessOnWidgetPort = async () => {
125427
+ try {
125428
+ child_process.execSync(`lsof -ti tcp:${CONFIG.PORT} | xargs kill -9`, {
125429
+ stdio: "ignore",
125430
+ });
125431
+ await sleep(1000);
125432
+ }
125433
+ catch {
125434
+ // No process on that port — nothing to kill.
125435
+ }
125436
+ };
125437
+ waitForServer = async (timeoutMs = CONFIG.MAX_WAIT_MS) => {
125438
+ const startTime = Date.now();
125439
+ while (Date.now() - startTime < timeoutMs) {
125440
+ if (this.process !== null && this.process.exitCode !== null) {
125441
+ throw new Error(`[NeetoChatWidget] Server process exited unexpectedly with code ${this.process.exitCode}.\nLog:\n${this.getLogContent()}`);
125442
+ }
125443
+ if (await httpCheck())
125444
+ return;
125445
+ await sleep(CONFIG.SERVER_POLL_INTERVAL_MS);
125446
+ }
125447
+ throw new Error(`[NeetoChatWidget] Widget did not start within ${timeoutMs / 1000}s.\nLog:\n${this.getLogContent()}`);
125448
+ };
125449
+ start = async () => {
125450
+ if (IS_STAGING_ENV)
125451
+ return;
125452
+ await this.withLock(async () => {
125453
+ const state = this.readState();
125454
+ if (await httpCheck()) {
125455
+ this.hasLease = true;
125456
+ this.writeState({ ...state, refCount: state.refCount + 1 });
125457
+ log("Server already running.");
125458
+ return;
125459
+ }
125460
+ this.ensureSetup();
125461
+ log("Starting server...");
125462
+ this.logStream = fs$4.createWriteStream(CONFIG.LOG);
125463
+ this.process = child_process.spawn("yarn", ["start"], {
125464
+ cwd: CONFIG.DIR,
125465
+ stdio: ["ignore", "pipe", "pipe"],
125466
+ });
125467
+ this.process.stdout?.pipe(this.logStream);
125468
+ this.process.stderr?.pipe(this.logStream);
125469
+ try {
125470
+ await this.waitForServer();
125471
+ }
125472
+ catch (error) {
125473
+ const pid = this.process?.pid;
125474
+ this.killServer(pid);
125475
+ pid && (await this.waitForProcessExit(pid));
125476
+ (await httpCheck()) && (await this.killProcessOnWidgetPort());
125477
+ this.process = null;
125478
+ this.closeLogStream();
125479
+ throw error;
125480
+ }
125481
+ this.hasLease = true;
125482
+ this.writeState({
125483
+ pid: this.process.pid ?? null,
125484
+ refCount: state.refCount + 1,
125485
+ });
125486
+ log("Server ready.");
125487
+ });
125488
+ };
125489
+ stop = async () => {
125490
+ if (IS_STAGING_ENV || !this.hasLease)
125491
+ return;
125492
+ await this.withLock(async () => {
125493
+ const state = this.readState();
125494
+ const nextRefCount = Math.max(state.refCount - 1, 0);
125495
+ this.hasLease = false;
125496
+ if (nextRefCount > 0) {
125497
+ this.writeState({ ...state, refCount: nextRefCount });
125498
+ return;
125499
+ }
125500
+ if (state.pid) {
125501
+ log("Stopping server...");
125502
+ this.killServer(state.pid);
125503
+ await this.waitForProcessExit(state.pid);
125504
+ (await httpCheck()) && (await this.killProcessOnWidgetPort());
125505
+ }
125506
+ this.process = null;
125507
+ this.closeLogStream();
125508
+ this.clearState();
125509
+ });
125510
+ };
125511
+ }
125512
+
125281
125513
  const NEETO_EMAIL_DELIVERY_TESTING_BASE_URL = "/neeto_email_delivery/api/v1/testing";
125282
125514
  class NeetoEmailDeliveryApi {
125283
125515
  neetoPlaywrightUtilities;
@@ -126226,6 +126458,7 @@ exports.CREDENTIALS = CREDENTIALS;
126226
126458
  exports.CURRENT_TIME_RANGES = CURRENT_TIME_RANGES;
126227
126459
  exports.CUSTOM_DOMAIN_SELECTORS = CUSTOM_DOMAIN_SELECTORS;
126228
126460
  exports.CUSTOM_DOMAIN_SUFFIX = CUSTOM_DOMAIN_SUFFIX;
126461
+ exports.ColorPickerUtils = ColorPickerUtils;
126229
126462
  exports.CustomCommands = CustomCommands;
126230
126463
  exports.CustomDomainApi = CustomDomainApi;
126231
126464
  exports.CustomDomainPage = CustomDomainPage;
@@ -126293,6 +126526,7 @@ exports.NEETO_ROUTES = NEETO_ROUTES;
126293
126526
  exports.NEETO_SEO_SELECTORS = NEETO_SEO_SELECTORS;
126294
126527
  exports.NEETO_TEXT_MODIFIER_SELECTORS = NEETO_TEXT_MODIFIER_SELECTORS;
126295
126528
  exports.NeetoAuthServer = NeetoAuthServer;
126529
+ exports.NeetoChatWidget = NeetoChatWidget;
126296
126530
  exports.NeetoEmailDeliveryApi = NeetoEmailDeliveryApi;
126297
126531
  exports.NeetoTowerApi = NeetoTowerApi;
126298
126532
  exports.ONBOARDING_SELECTORS = ONBOARDING_SELECTORS;