@wdio/shared-store-service
Version:
A WebdriverIO service to exchange data across processes
269 lines (261 loc) • 8.73 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/server.ts
var server_exports = {};
__export(server_exports, {
__resourcePoolStore: () => __resourcePoolStore,
__store: () => __store,
startServer: () => startServer
});
import polka from "polka";
import { json } from "@polka/parse";
var store, resourcePoolStore, __store, __resourcePoolStore, validateBody, MAX_TIMEOUT, DEFAULT_TIMEOUT, startServer;
var init_server = __esm({
"src/server.ts"() {
"use strict";
store = {};
resourcePoolStore = /* @__PURE__ */ new Map();
__store = store;
__resourcePoolStore = resourcePoolStore;
validateBody = (req, res, next) => {
if (!req.path.endsWith("/get") && !req.path.endsWith("/set")) {
return next();
}
if (req.method === "POST" && typeof req.body.key !== "string") {
res.end(JSON.stringify({ error: "Invalid payload, key is required." }));
}
next();
};
MAX_TIMEOUT = 15e3;
DEFAULT_TIMEOUT = 1e3;
startServer = () => new Promise((resolve, reject) => {
const app = polka().use(json(), validateBody).post("/", (req, res) => {
const key = req.body.key;
if (key === "*") {
throw new Error(`You can't set a value with key "*" as this is a reserved key`);
}
store[key] = req.body.value;
return res.end();
}).get("/:key", (req, res) => {
const key = req.params.key;
const value = key === "*" ? store : store[key];
res.end(JSON.stringify({ value }));
}).post("/pool", (req, res, next) => {
const key = req.body.key;
const value = req.body.value;
if (!Array.isArray(value)) {
return next("Resource pool must be an array of values");
}
resourcePoolStore.set(key, value);
return res.end();
}).get("/pool/:key", async (req, res, next) => {
const key = req.params.key;
if (!resourcePoolStore.has(key)) {
return next(`'${key}' resource pool does not exist. Set it first using 'setResourcePool'`);
}
let pool = resourcePoolStore.get(key) || [];
if (pool.length > 0) {
return res.end(JSON.stringify({ value: pool.shift() }));
}
const timeout = Math.min(parseInt(req.query.timeout) || DEFAULT_TIMEOUT, MAX_TIMEOUT);
try {
const result = await new Promise((resolve2, reject2) => {
setTimeout(function secondAttempt() {
pool = resourcePoolStore.get(key) || [];
if (pool.length > 0) {
resolve2({ value: pool.shift() });
}
reject2(`'${key}' resource pool is empty. Set values to it first using 'setResourcePool' or 'addValueToPool'`);
}, timeout);
});
res.end(JSON.stringify(result));
} catch (err) {
return next(err);
}
}).post("/pool/:key", (req, res, next) => {
const key = req.params.key;
const value = req.body.value;
const pool = resourcePoolStore.get(key);
if (!pool) {
return next(`'${key}' resource pool does not exist. Set it first using 'setResourcePool'`);
}
pool.push(value);
return res.end();
});
app.listen(0, (err) => {
if (err) {
return reject(err);
}
resolve({ app, port: app.server.address().port });
});
});
}
});
// src/launcher.ts
import logger from "@wdio/logger";
// src/client.ts
var baseUrlResolve;
var baseUrlPromise = new Promise((resolve) => {
baseUrlResolve = resolve;
});
var headers = {
"Content-Type": "application/json"
};
var isBaseUrlReady = false;
var setPort = (port) => {
baseUrlResolve(`http://localhost:${port}`);
isBaseUrlReady = true;
};
var getValue = async (key) => {
if (!isBaseUrlReady) {
throw new Error("Attempting to use `getValue` before the server has been initialized.");
}
const baseUrl = await baseUrlPromise;
const res = await fetch(`${baseUrl}/${key}`, {
method: "get",
headers
}).catch(errHandler);
const responseBody = await res.json();
return responseBody.value ?? void 0;
};
var setValue = async (key, value) => {
const setPromise = baseUrlPromise.then((baseUrl) => {
return fetch(`${baseUrl}/`, {
method: "post",
body: JSON.stringify({ key, value }),
headers
}).catch(errHandler);
});
return isBaseUrlReady ? (await setPromise).status : Promise.resolve();
};
var setResourcePool = async (key, value) => {
const setPromise = baseUrlPromise.then((baseUrl) => {
return fetch(`${baseUrl}/pool`, {
method: "post",
body: JSON.stringify({ key, value }),
headers
}).catch(errHandler);
});
return isBaseUrlReady ? (await setPromise).status : Promise.resolve();
};
var getValueFromPool = async (key, options) => {
if (!isBaseUrlReady) {
throw new Error("Attempting to use `getValueFromPool` before the server has been initialized.");
}
const baseUrl = await baseUrlPromise;
const res = await fetch(`${baseUrl}/pool/${key}${typeof options?.timeout === "number" ? `?timeout=${options.timeout}` : ""}`, {
method: "get",
headers
}).catch(errHandler);
const responseBody = await res.json();
return responseBody.value ?? void 0;
};
var addValueToPool = async (key, value) => {
if (!isBaseUrlReady) {
throw new Error("Attempting to use `addValueToPool` before the server has been initialized.");
}
const baseUrl = await baseUrlPromise;
const res = await fetch(`${baseUrl}/pool/${key}`, {
method: "post",
body: JSON.stringify({ value }),
headers
}).catch(errHandler);
return res.status;
};
var errHandler = async (err) => {
throw new Error(`${err.message || "Shared store server threw an error"}`);
};
// src/constants.ts
var CUSTOM_CAP = "wdio:sharedStoreServicePort";
// src/launcher.ts
var log = logger("@wdio/shared-store-service");
var server;
var SharedStoreLauncher = class {
_app;
async onPrepare(_, capabilities) {
server = await Promise.resolve().then(() => (init_server(), server_exports));
const { port, app } = await server.startServer();
this._app = app;
setPort(port);
const capsList = Array.isArray(capabilities) ? capabilities : Object.values(capabilities).map((multiremoteOption) => multiremoteOption.capabilities);
const caps = capsList.flatMap((c) => {
const multiremote = c;
if (!multiremote.browserName && multiremote[Object.keys(multiremote)[0]].capabilities) {
return Object.values(multiremote).map(
(options) => options.capabilities?.alwaysMatch || options.capabilities
);
}
const w3cCaps = c;
return w3cCaps.alwaysMatch || c;
});
caps.forEach((c) => {
c[CUSTOM_CAP] = port;
});
log.info(`Started shared server on port ${port}`);
}
async onComplete() {
return new Promise((resolve) => {
if (this._app && this._app.server.close) {
this._app.server.close(() => resolve());
}
return resolve();
});
}
};
// src/service.ts
var SharedStoreService = class {
_browser;
constructor(_, caps) {
const port = caps[CUSTOM_CAP] || caps.alwaysMatch?.[CUSTOM_CAP] || (Object.values(caps)[0]?.capabilities)[CUSTOM_CAP];
if (!port) {
throw new Error("SharedStoreService: port not found in capabilities");
}
setPort(port);
}
before(caps, specs, _browser) {
this._browser = _browser;
const sharedStore = Object.create({}, {
get: {
value: (key) => getValue(key)
},
set: {
value: (key, value) => setValue(key, value)
},
setResourcePool: {
value: (key, value) => setResourcePool(key, value)
},
getValueFromPool: {
value: (key, options) => getValueFromPool(key, options)
},
addValueToPool: {
value: (key, value) => addValueToPool(key, value)
}
});
this._browser.sharedStore = sharedStore;
const browser = this._browser;
if (!this._browser.capabilities && browser.instances) {
browser.instances.forEach((browserName) => {
browser.getInstance(browserName).sharedStore = sharedStore;
});
}
}
};
// src/index.ts
var index_default = SharedStoreService;
var launcher = SharedStoreLauncher;
export {
addValueToPool,
index_default as default,
getValue,
getValueFromPool,
launcher,
setResourcePool,
setValue
};