@supatest/wdio-appium-reporter 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +165 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +165 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6534,6 +6534,8 @@ var AttachmentUploader = class {
|
|
|
6534
6534
|
var import_reporter = __toESM(require("@wdio/reporter"));
|
|
6535
6535
|
var VIDEO_WAIT_TIMEOUT_MS = 6e4;
|
|
6536
6536
|
var VIDEO_POLL_INTERVAL_MS = 1e3;
|
|
6537
|
+
var COORDINATOR_LOCK_TIMEOUT_MS = 1e4;
|
|
6538
|
+
var COORDINATOR_LOCK_POLL_MS = 50;
|
|
6537
6539
|
var SupatestAppiumReporter = class extends import_reporter.default {
|
|
6538
6540
|
reporterOptions;
|
|
6539
6541
|
client;
|
|
@@ -6564,6 +6566,9 @@ var SupatestAppiumReporter = class extends import_reporter.default {
|
|
|
6564
6566
|
runCompletePromise;
|
|
6565
6567
|
runComplete = false;
|
|
6566
6568
|
unregisterInterruptHandler;
|
|
6569
|
+
coordinatorPath;
|
|
6570
|
+
currentSpecKey = "unknown";
|
|
6571
|
+
expectedSpecs = [];
|
|
6567
6572
|
testsForVideoUpload = [];
|
|
6568
6573
|
// biome-ignore lint/suspicious/noExplicitAny: WDIOReporter options type is overly strict; we need to inject writeStream default
|
|
6569
6574
|
constructor(options) {
|
|
@@ -6582,7 +6587,8 @@ var SupatestAppiumReporter = class extends import_reporter.default {
|
|
|
6582
6587
|
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
6583
6588
|
dryRun: options.dryRun ?? process.env.SUPATEST_DRY_RUN === "true",
|
|
6584
6589
|
screenshotDir: options.screenshotDir,
|
|
6585
|
-
videoDir: options.videoDir
|
|
6590
|
+
videoDir: options.videoDir,
|
|
6591
|
+
runGroupId: options.runGroupId || process.env.SUPATEST_RUN_GROUP_ID
|
|
6586
6592
|
};
|
|
6587
6593
|
}
|
|
6588
6594
|
onRunnerStart(runner) {
|
|
@@ -6630,6 +6636,10 @@ var SupatestAppiumReporter = class extends import_reporter.default {
|
|
|
6630
6636
|
this.uploadLimit = pLimit(this.reporterOptions.maxConcurrentUploads);
|
|
6631
6637
|
const capabilities = this.extractCapabilities(runner);
|
|
6632
6638
|
const specs = runner.specs || [];
|
|
6639
|
+
this.expectedSpecs = this.getExpectedSpecs(runner);
|
|
6640
|
+
this.currentSpecKey = this.getCurrentSpecKey(runner);
|
|
6641
|
+
this.coordinatorPath = this.getCoordinatorPath(runner);
|
|
6642
|
+
const runSpecs = this.expectedSpecs.length > 0 ? this.expectedSpecs : specs;
|
|
6633
6643
|
try {
|
|
6634
6644
|
const runRequest = {
|
|
6635
6645
|
projectId: this.reporterOptions.projectId,
|
|
@@ -6638,10 +6648,10 @@ var SupatestAppiumReporter = class extends import_reporter.default {
|
|
|
6638
6648
|
version: this.getAppiumVersion(),
|
|
6639
6649
|
framework: this.getFramework(runner),
|
|
6640
6650
|
capabilities,
|
|
6641
|
-
specs
|
|
6651
|
+
specs: runSpecs
|
|
6642
6652
|
},
|
|
6643
6653
|
testStats: {
|
|
6644
|
-
totalFiles:
|
|
6654
|
+
totalFiles: runSpecs.length,
|
|
6645
6655
|
totalTests: 0,
|
|
6646
6656
|
totalProjects: 1
|
|
6647
6657
|
},
|
|
@@ -6649,15 +6659,14 @@ var SupatestAppiumReporter = class extends import_reporter.default {
|
|
|
6649
6659
|
git: await this.getGitInfo(),
|
|
6650
6660
|
rootDir: this.rootDir
|
|
6651
6661
|
};
|
|
6652
|
-
|
|
6653
|
-
this.runId = response.runId;
|
|
6662
|
+
this.runId = await this.getOrCreateRun(runRequest);
|
|
6654
6663
|
this.unregisterInterruptHandler = registerInterruptHandler(
|
|
6655
6664
|
this.client,
|
|
6656
6665
|
() => this.runId
|
|
6657
6666
|
);
|
|
6658
6667
|
const platform = capabilities.platformName || "unknown";
|
|
6659
6668
|
const device = capabilities.deviceName || "unknown device";
|
|
6660
|
-
logInfo(`Appium run ${this.runId} started [${platform} / ${device}] (${
|
|
6669
|
+
logInfo(`Appium run ${this.runId} started [${platform} / ${device}] (${runSpecs.length} spec files)`);
|
|
6661
6670
|
} catch (error) {
|
|
6662
6671
|
const errorMsg = getErrorMessage2(error);
|
|
6663
6672
|
this.errorCollector.recordError("RUN_CREATE", errorMsg, { error });
|
|
@@ -7046,6 +7055,11 @@ var SupatestAppiumReporter = class extends import_reporter.default {
|
|
|
7046
7055
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7047
7056
|
const startTime = this.startedAt ? new Date(this.startedAt).getTime() : 0;
|
|
7048
7057
|
this.unregisterInterruptHandler?.();
|
|
7058
|
+
const shouldCompleteRun = await this.markSpecComplete();
|
|
7059
|
+
if (!shouldCompleteRun) {
|
|
7060
|
+
logInfo(`Appium run ${this.runId} still has pending spec files; skipping run completion in this worker`);
|
|
7061
|
+
return;
|
|
7062
|
+
}
|
|
7049
7063
|
try {
|
|
7050
7064
|
await this.client.completeRun(this.runId, {
|
|
7051
7065
|
status: this.mapRunStatus(runner),
|
|
@@ -7179,6 +7193,151 @@ var SupatestAppiumReporter = class extends import_reporter.default {
|
|
|
7179
7193
|
if (this.currentRunner?.specs?.[0]) return this.currentRunner.specs[0];
|
|
7180
7194
|
return "unknown";
|
|
7181
7195
|
}
|
|
7196
|
+
getExpectedSpecs(runner) {
|
|
7197
|
+
const configSpecs = this.flattenSpecEntries(runner.config?.specs);
|
|
7198
|
+
const expandedConfigSpecs = configSpecs.flatMap((spec) => this.expandSpecPattern(spec));
|
|
7199
|
+
const runnerSpecs = runner.specs || [];
|
|
7200
|
+
const specs = expandedConfigSpecs.length > 0 ? expandedConfigSpecs : runnerSpecs;
|
|
7201
|
+
return Array.from(new Set(specs.map((spec) => this.normalizeSpecPath(spec)))).sort();
|
|
7202
|
+
}
|
|
7203
|
+
flattenSpecEntries(value) {
|
|
7204
|
+
if (typeof value === "string") return [value];
|
|
7205
|
+
if (!Array.isArray(value)) return [];
|
|
7206
|
+
return value.flatMap((entry) => this.flattenSpecEntries(entry));
|
|
7207
|
+
}
|
|
7208
|
+
isGlobSpec(spec) {
|
|
7209
|
+
return /[*?[\]{}()!+@]/.test(spec);
|
|
7210
|
+
}
|
|
7211
|
+
expandSpecPattern(spec) {
|
|
7212
|
+
if (!this.isGlobSpec(spec)) return [spec];
|
|
7213
|
+
const absoluteSpec = import_node_path2.default.isAbsolute(spec) ? spec : import_node_path2.default.join(this.rootDir, spec);
|
|
7214
|
+
const firstGlobIndex = absoluteSpec.search(/[*?[\]{}()!+@]/);
|
|
7215
|
+
const basePrefix = firstGlobIndex >= 0 ? absoluteSpec.slice(0, firstGlobIndex) : absoluteSpec;
|
|
7216
|
+
const baseDir = /[\\/]$/.test(basePrefix) ? basePrefix.replace(/[\\/]$/, "") : import_node_path2.default.dirname(basePrefix);
|
|
7217
|
+
if (!import_node_fs.default.existsSync(baseDir)) return [];
|
|
7218
|
+
const extensionMatch = spec.match(/\.([a-zA-Z0-9]+)$/);
|
|
7219
|
+
const expectedExtension = extensionMatch ? `.${extensionMatch[1]}` : void 0;
|
|
7220
|
+
const files = [];
|
|
7221
|
+
const visit = (dir) => {
|
|
7222
|
+
for (const entry of import_node_fs.default.readdirSync(dir, { withFileTypes: true })) {
|
|
7223
|
+
const entryPath = import_node_path2.default.join(dir, entry.name);
|
|
7224
|
+
if (entry.isDirectory()) {
|
|
7225
|
+
visit(entryPath);
|
|
7226
|
+
continue;
|
|
7227
|
+
}
|
|
7228
|
+
if (!expectedExtension || entryPath.endsWith(expectedExtension)) {
|
|
7229
|
+
files.push(entryPath);
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
};
|
|
7233
|
+
visit(baseDir);
|
|
7234
|
+
return files.sort();
|
|
7235
|
+
}
|
|
7236
|
+
normalizeSpecPath(spec) {
|
|
7237
|
+
const normalized = import_node_path2.default.isAbsolute(spec) ? import_node_path2.default.relative(this.rootDir, spec) : spec;
|
|
7238
|
+
return normalized.replace(/^[.][\\/]/, "").split(import_node_path2.default.sep).join("/");
|
|
7239
|
+
}
|
|
7240
|
+
getCurrentSpecKey(runner) {
|
|
7241
|
+
const specs = runner.specs || [];
|
|
7242
|
+
if (specs.length === 0) return runner.cid || "unknown";
|
|
7243
|
+
return specs.map((spec) => this.normalizeSpecPath(spec)).sort().join("|");
|
|
7244
|
+
}
|
|
7245
|
+
getCoordinatorPath(runner) {
|
|
7246
|
+
if (this.reporterOptions.dryRun) return void 0;
|
|
7247
|
+
const env = this.getEnvironmentInfo();
|
|
7248
|
+
const ciKey = env.ci ? [env.ci.provider, env.ci.runId, env.ci.jobId, env.ci.buildNumber].filter(Boolean).join(":") : "";
|
|
7249
|
+
const groupId = this.reporterOptions.runGroupId || ciKey || `local:${process.ppid}`;
|
|
7250
|
+
const configSpecs = this.flattenSpecEntries(runner.config?.specs).map((spec) => this.normalizeSpecPath(spec)).sort().join("|");
|
|
7251
|
+
const key = hashKey([
|
|
7252
|
+
this.reporterOptions.projectId,
|
|
7253
|
+
this.rootDir,
|
|
7254
|
+
groupId,
|
|
7255
|
+
configSpecs
|
|
7256
|
+
].join("::"));
|
|
7257
|
+
return import_node_path2.default.join(import_node_os.default.tmpdir(), "supatest-wdio-appium-runs", `${key}.json`);
|
|
7258
|
+
}
|
|
7259
|
+
async getOrCreateRun(runRequest) {
|
|
7260
|
+
if (!this.coordinatorPath) {
|
|
7261
|
+
const response = await this.client.createRun(runRequest);
|
|
7262
|
+
return response.runId;
|
|
7263
|
+
}
|
|
7264
|
+
return this.withCoordinatorLock(async () => {
|
|
7265
|
+
const state = await this.readCoordinatorState();
|
|
7266
|
+
if (state.runId) return state.runId;
|
|
7267
|
+
const response = await this.client.createRun(runRequest);
|
|
7268
|
+
await this.writeCoordinatorState({
|
|
7269
|
+
runId: response.runId,
|
|
7270
|
+
startedAt: this.startedAt,
|
|
7271
|
+
expectedSpecs: this.expectedSpecs,
|
|
7272
|
+
completedSpecs: state.completedSpecs || []
|
|
7273
|
+
});
|
|
7274
|
+
return response.runId;
|
|
7275
|
+
});
|
|
7276
|
+
}
|
|
7277
|
+
async markSpecComplete() {
|
|
7278
|
+
if (!this.coordinatorPath) return true;
|
|
7279
|
+
return this.withCoordinatorLock(async () => {
|
|
7280
|
+
const state = await this.readCoordinatorState();
|
|
7281
|
+
const completed = new Set(state.completedSpecs || []);
|
|
7282
|
+
completed.add(this.currentSpecKey);
|
|
7283
|
+
const expected = state.expectedSpecs.length > 0 ? state.expectedSpecs : this.expectedSpecs;
|
|
7284
|
+
const complete = expected.length <= 1 || expected.every((spec) => completed.has(spec));
|
|
7285
|
+
await this.writeCoordinatorState({
|
|
7286
|
+
...state,
|
|
7287
|
+
runId: state.runId || this.runId,
|
|
7288
|
+
startedAt: state.startedAt || this.startedAt,
|
|
7289
|
+
expectedSpecs: expected,
|
|
7290
|
+
completedSpecs: Array.from(completed).sort()
|
|
7291
|
+
});
|
|
7292
|
+
return complete;
|
|
7293
|
+
});
|
|
7294
|
+
}
|
|
7295
|
+
async readCoordinatorState() {
|
|
7296
|
+
if (!this.coordinatorPath) {
|
|
7297
|
+
return { expectedSpecs: this.expectedSpecs, completedSpecs: [] };
|
|
7298
|
+
}
|
|
7299
|
+
try {
|
|
7300
|
+
const raw = await import_node_fs.default.promises.readFile(this.coordinatorPath, "utf-8");
|
|
7301
|
+
const parsed = JSON.parse(raw);
|
|
7302
|
+
return {
|
|
7303
|
+
runId: parsed.runId,
|
|
7304
|
+
startedAt: parsed.startedAt,
|
|
7305
|
+
expectedSpecs: parsed.expectedSpecs || this.expectedSpecs,
|
|
7306
|
+
completedSpecs: parsed.completedSpecs || []
|
|
7307
|
+
};
|
|
7308
|
+
} catch {
|
|
7309
|
+
return { expectedSpecs: this.expectedSpecs, completedSpecs: [] };
|
|
7310
|
+
}
|
|
7311
|
+
}
|
|
7312
|
+
async writeCoordinatorState(state) {
|
|
7313
|
+
if (!this.coordinatorPath) return;
|
|
7314
|
+
await import_node_fs.default.promises.mkdir(import_node_path2.default.dirname(this.coordinatorPath), { recursive: true });
|
|
7315
|
+
await import_node_fs.default.promises.writeFile(this.coordinatorPath, JSON.stringify(state, null, 2));
|
|
7316
|
+
}
|
|
7317
|
+
async withCoordinatorLock(fn) {
|
|
7318
|
+
if (!this.coordinatorPath) return fn();
|
|
7319
|
+
await import_node_fs.default.promises.mkdir(import_node_path2.default.dirname(this.coordinatorPath), { recursive: true });
|
|
7320
|
+
const lockPath = `${this.coordinatorPath}.lock`;
|
|
7321
|
+
const start = Date.now();
|
|
7322
|
+
while (true) {
|
|
7323
|
+
let handle;
|
|
7324
|
+
try {
|
|
7325
|
+
handle = await import_node_fs.default.promises.open(lockPath, "wx");
|
|
7326
|
+
try {
|
|
7327
|
+
return await fn();
|
|
7328
|
+
} finally {
|
|
7329
|
+
await handle.close();
|
|
7330
|
+
await import_node_fs.default.promises.rm(lockPath, { force: true });
|
|
7331
|
+
}
|
|
7332
|
+
} catch (error) {
|
|
7333
|
+
if (error.code !== "EEXIST") throw error;
|
|
7334
|
+
if (Date.now() - start > COORDINATOR_LOCK_TIMEOUT_MS) {
|
|
7335
|
+
await import_node_fs.default.promises.rm(lockPath, { force: true });
|
|
7336
|
+
}
|
|
7337
|
+
await new Promise((resolve) => setTimeout(resolve, COORDINATOR_LOCK_POLL_MS));
|
|
7338
|
+
}
|
|
7339
|
+
}
|
|
7340
|
+
}
|
|
7182
7341
|
extractTags(title) {
|
|
7183
7342
|
const tags = [];
|
|
7184
7343
|
const seenIndices = /* @__PURE__ */ new Set();
|