mattermost-mobile-upstream/detox/utils/merge-jest-results-for-tsio.test.js
yasser khan ebc91be6e0
E2E: run shared channels tests on CI, demo plugin v0.12.2 on all full-suite servers (#10267)
* E2E: run the shared channels tests on CI instead of skipping them

CI servers now boot with Shared Channels running (matterwick#106), so the suite runs there and fails setup if the service is missing.

* E2E: run demo plugin v0.12.2 in DialogOnlyMode

Moves the demo plugin to v0.12.2 and writes DialogOnlyMode: true into its settings entry before it is installed on server B, so it no longer creates a demo user and channels or posts in every team on every server event. Only /dialog is kept, which is all the dialog specs use. The entry still keeps an older build on a reused server out of config saves.

* E2E: give a neutral reason when Shared Channels is unavailable

hasSharedChannels is false both when the license lacks Shared Channels and when GET /remotecluster fails; point to the globalSetup log line that names the actual reason.

* E2E: run the read-only dialog specs on the pool servers

With demo plugin v0.12.2 in DialogOnlyMode it no longer posts in every team, so it is installed on every full-suite server (pool A and mutation B) instead of B only. interactive_dialog_plugin and interactive_dialog_collapsible change no server-wide state, so they leave the mutation manifest and run on the pool. interactive_dialog_mobile_upload_disabled stays on B. The shared SITE_3 and smoke servers still get no demo plugin, and CMT still rejects a site 2 that is also a pool site 1.

* E2E: remove unused demo plugin helpers

apiUploadAndEnablePlugin and apiClearDemoPluginFileUploadKV had no callers (the KV route does not exist). Provisioning installs the plugin, so the install plan they used goes too.

* fix(e2e): say why a spec has no result (#10280)

* chore: Update NOTICE.txt file with updated dependencies (#10277)

Co-authored-by: unified-ci-app[bot] <121569378+unified-ci-app[bot]@users.noreply.github.com>

* fix(e2e): say whether a spec with no result had a silent shard or a stopped one

A spec that produced no result now says which: no spec of its shard reported
(the shard ran or uploaded nothing), or other specs of its shard reported (its
Jest process stopped before reaching this spec). E2E triage re-runs the first
and judges the second by the spec's own history. The shard of each spec comes
from the generate-specs matrix rows; without them the old wording stays.

---------

Co-authored-by: unified-ci-app[bot] <121569378+unified-ci-app[bot]@users.noreply.github.com>

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: unified-ci-app[bot] <121569378+unified-ci-app[bot]@users.noreply.github.com>
2026-10-27 11:42:07 +05:30

233 lines
10 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// CI util unit tests: run with `node --test detox/utils/merge-jest-results-for-tsio.test.js`.
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const {describe, it} = require('node:test');
const {
mergeJestResultsForTsio,
toTsioDetoxSuite,
relativizeDetoxPath,
writeMergedJestResultsForTsio,
appendUnreportedSpecs,
parseExpectedSpecs,
parseShards,
} = require('./merge-jest-results-for-tsio');
describe('merge-jest-results-for-tsio', () => {
it('should keep already-relative paths (any e2e layout) unchanged', () => {
assert.equal(
relativizeDetoxPath('detox/e2e/test/foo.e2e.ts'),
'detox/e2e/test/foo.e2e.ts',
);
assert.equal(
relativizeDetoxPath('e2e/detox/test/foo.e2e.ts'),
'e2e/detox/test/foo.e2e.ts',
);
assert.equal(
relativizeDetoxPath('e2e/maestro/flows/calls/mute.yml'),
'e2e/maestro/flows/calls/mute.yml',
);
});
it('should strip absolute paths against an explicit repo root', () => {
assert.equal(
relativizeDetoxPath(
'/home/runner/work/mattermost-mobile/mattermost-mobile/e2e/detox/test/foo.e2e.ts',
{repoRoot: '/home/runner/work/mattermost-mobile/mattermost-mobile'},
),
'e2e/detox/test/foo.e2e.ts',
);
assert.equal(
relativizeDetoxPath(
'/home/runner/work/mattermost-mobile/mattermost-mobile/detox/e2e/test/foo.e2e.ts',
{repoRoot: '/home/runner/work/mattermost-mobile/mattermost-mobile'},
),
'detox/e2e/test/foo.e2e.ts',
);
});
it('should fall back to GitHub Actions /work/<repo>/<repo>/ stripping', () => {
assert.equal(
relativizeDetoxPath(
'/home/runner/work/mattermost-mobile/mattermost-mobile/e2e/detox/test/foo.e2e.ts',
{repoRoot: '/tmp/not-the-root'},
),
'e2e/detox/test/foo.e2e.ts',
);
});
it('should convert Jest CLI suites (name + assertionResults) to TSIO Detox shape', () => {
const converted = toTsioDetoxSuite({
name: '/repo/e2e/detox/test/products/agents/agent_mention.e2e.ts',
startTime: 1000,
assertionResults: [{
ancestorTitles: ['Autocomplete - Agent Mention'],
duration: 12,
failureMessages: ['boom'],
fullName: 'Autocomplete - Agent Mention should show bot',
status: 'failed',
title: 'should show bot',
}],
}, {repoRoot: '/repo'});
assert.deepEqual(converted, {
testFilePath: 'e2e/detox/test/products/agents/agent_mention.e2e.ts',
perfStats: {start: 1000},
testResults: [{
ancestorTitles: ['Autocomplete - Agent Mention'],
duration: 12,
failureMessages: ['boom'],
fullName: 'Autocomplete - Agent Mention should show bot',
status: 'failed',
title: 'should show bot',
}],
});
});
it('should keep a retried test\'s earlier failures for TSIO, and nothing for an untried one', () => {
const converted = toTsioDetoxSuite({
name: '/repo/detox/e2e/test/threads/reply_to_thread.e2e.ts',
assertionResults: [
{fullName: 'Threads MM-T4809_1', title: 'MM-T4809_1', status: 'passed', failureMessages: [], retryReasons: ['Test Failed: No elements found']},
{fullName: 'Threads MM-T4809_2', title: 'MM-T4809_2', status: 'passed', failureMessages: [], retryReasons: []},
],
}, {repoRoot: '/repo'});
assert.deepEqual(converted.testResults[0].retryReasons, ['Test Failed: No elements found']);
assert.equal('retryReasons' in converted.testResults[1], false);
});
it('should pass through suites already in TSIO Detox shape', () => {
const converted = toTsioDetoxSuite({
testFilePath: 'e2e/detox/test/foo.e2e.ts',
testResults: [{
ancestorTitles: [],
duration: 1,
failureMessages: [],
fullName: 'foo',
status: 'passed',
title: 'foo',
}],
});
assert.equal(converted.testFilePath, 'e2e/detox/test/foo.e2e.ts');
assert.equal(converted.testResults.length, 1);
assert.equal(converted.testResults[0].status, 'passed');
});
it('should merge multiple CLI shard files into one TSIO payload with real test cases', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tsio-merge-'));
const a = path.join(dir, 'a.json');
const b = path.join(dir, 'b.json');
fs.writeFileSync(a, JSON.stringify({
numTotalTests: 1,
testResults: [{
name: '/x/e2e/detox/test/a.e2e.ts',
assertionResults: [{
ancestorTitles: ['A'],
duration: 1,
failureMessages: [],
fullName: 'A pass',
status: 'passed',
title: 'pass',
}],
}],
}));
fs.writeFileSync(b, JSON.stringify({
numTotalTests: 1,
testResults: [{
name: '/x/e2e/detox/test/b.e2e.ts',
assertionResults: [{
ancestorTitles: ['B'],
duration: 2,
failureMessages: ['fail'],
fullName: 'B fail',
status: 'failed',
title: 'fail',
}],
}],
}));
const opts = {repoRoot: '/x'};
const merged = mergeJestResultsForTsio([a, b], opts);
assert.equal(merged.testResults.length, 2);
assert.equal(merged.testResults[0].testFilePath, 'e2e/detox/test/a.e2e.ts');
assert.equal(merged.testResults[0].testResults[0].status, 'passed');
assert.equal(merged.testResults[1].testFilePath, 'e2e/detox/test/b.e2e.ts');
assert.equal(merged.testResults[1].testResults[0].status, 'failed');
assert.equal(merged.testResults[0].assertionResults, undefined);
assert.equal(merged.testResults[0].name, undefined);
const out = path.join(dir, 'merged.json');
const stats = writeMergedJestResultsForTsio([a, b], out, opts);
assert.deepEqual(stats, {suites: 2, tests: 2});
assert.equal(JSON.parse(fs.readFileSync(out, 'utf8')).testResults.length, 2);
fs.rmSync(dir, {recursive: true, force: true});
});
it('should mark specs that never reported as failed', () => {
// main d943628: machine-4's Jest was killed mid-run, jest-stare's partial report
// was promoted, and 3 specs vanished from a report that published 100% green.
const merged = {testResults: [{testFilePath: 'detox/e2e/test/a.e2e.ts', testResults: [{status: 'passed'}]}]};
const missing = appendUnreportedSpecs(merged, [
'detox/e2e/test/a.e2e.ts',
'detox/e2e/test/b.e2e.ts',
'detox/e2e/test/c.e2e.ts',
]);
assert.deepEqual(missing, ['detox/e2e/test/b.e2e.ts', 'detox/e2e/test/c.e2e.ts']);
assert.equal(merged.testResults.length, 3, 'every assigned spec must appear');
assert.equal(merged.testResults[1].testResults[0].status, 'failed');
assert.match(merged.testResults[1].testResults[0].failureMessages[0], /produced no result/);
});
it('should say whether the spec\'s shard reported nothing or stopped before reaching it', () => {
// Triage re-runs a shard that reported nothing, and judges a spec whose shard stopped before it by its history.
const merged = {testResults: [{testFilePath: 'detox/e2e/test/a.e2e.ts', testResults: [{status: 'passed'}]}]};
const shards = [['detox/e2e/test/a.e2e.ts', 'detox/e2e/test/b.e2e.ts'], ['detox/e2e/test/c.e2e.ts', 'detox/e2e/test/d.e2e.ts']];
appendUnreportedSpecs(merged, shards.flat(), shards);
const reason = (spec) => merged.testResults.find((s) => s.testFilePath === spec).testResults[0].failureMessages[0];
assert.equal(reason('detox/e2e/test/b.e2e.ts'), 'detox/e2e/test/b.e2e.ts was assigned to a shard but produced no result: other specs of its shard reported, so its Jest process stopped before reaching this spec.');
assert.equal(reason('detox/e2e/test/c.e2e.ts'), 'detox/e2e/test/c.e2e.ts was assigned to a shard but produced no result: no spec of its shard reported, so the shard ran or uploaded nothing.');
assert.match(reason('detox/e2e/test/d.e2e.ts'), /no spec of its shard reported/);
});
it('should read each generate-specs row as one shard', () => {
assert.deepEqual(parseShards(JSON.stringify({include: [{specs: 'a.e2e.ts b.e2e.ts'}, {specs: 'c.e2e.ts'}, {}]})), [['a.e2e.ts', 'b.e2e.ts'], ['c.e2e.ts']]);
assert.deepEqual(parseShards(''), []);
});
it('should not touch a complete report', () => {
const merged = {testResults: [{testFilePath: 'detox/e2e/test/a.e2e.ts'}]};
assert.deepEqual(appendUnreportedSpecs(merged, ['detox/e2e/test/a.e2e.ts']), []);
assert.equal(merged.testResults.length, 1);
});
it('should mark every spec failed when no shard reported at all', () => {
// If the merge exits before this, TSIO never receives a report and the commit
// status stays pending instead of failing.
const merged = mergeJestResultsForTsio([]);
const missing = appendUnreportedSpecs(merged, ['detox/e2e/test/a.e2e.ts', 'detox/e2e/test/b.e2e.ts']);
assert.equal(missing.length, 2);
assert.equal(merged.testResults.length, 2, 'a run with no shard reports must still emit a report');
});
it('should flatten a generate-specs matrix and de-duplicate', () => {
const specs = parseExpectedSpecs(JSON.stringify({include: [
{runId: 1, specs: 'a.e2e.ts b.e2e.ts'},
{runId: 2, specs: 'b.e2e.ts c.e2e.ts'},
]}));
assert.deepEqual(specs, ['a.e2e.ts', 'b.e2e.ts', 'c.e2e.ts']);
assert.deepEqual(parseExpectedSpecs(''), []);
assert.deepEqual(parseExpectedSpecs('{}'), []);
});
});