snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 00:29:55 +05:00
commit 240db4e2a7
15309 changed files with 2173406 additions and 0 deletions

View File

@@ -0,0 +1,925 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
expect,
isWebhookTestServerReachable,
setupWebhookTestServer,
test,
testConfig,
} from '@mattermost/playwright-lib';
test.describe('Interactive mm_blocks (ephemeral post)', () => {
test(
'renders mm_blocks in an ephemeral post created via API',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral']},
async ({pw}) => {
const {team, user, adminClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const marker = `E2E mm_blocks ephemeral ${pw.random.id()}`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
message: 'E2E mm_blocks (ephemeral)',
props: {
mm_blocks: [
{type: 'text', text: marker},
{type: 'divider'},
{type: 'text', text: 'Second line after divider.'},
],
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
await expect(lastPost.container.getByText('(Only visible to you)', {exact: true})).toBeVisible();
await expect(lastPost.container.locator('.mm-blocks')).toBeVisible();
await expect(lastPost.container.getByText(marker)).toBeVisible();
await expect(lastPost.container.getByText('Second line after divider.')).toBeVisible();
},
);
test(
'ephemeral mm_blocks external action reaches webhook sidecar and shows integration ephemeral',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral', '@external_service']},
async ({pw, request}) => {
test.skip(
!(await isWebhookTestServerReachable(request)),
[
`Webhook test server is not reachable at ${testConfig.webhookBaseUrl}.`,
'Start it from the repo: cd e2e-tests/cypress && npm run start:webhook',
'Or set PW_WEBHOOK_BASE_URL when it runs elsewhere.',
].join('\n'),
);
await setupWebhookTestServer(request, {
mattermostBaseUrl: testConfig.internalBaseURL,
adminUsername: testConfig.adminUsername,
adminPassword: testConfig.adminPassword,
});
const {team, user, adminClient, userClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// Ephemeral roots have no Reply control; under CRT, integration ephemerals stay out of the center list.
// Thread the interactive ephemeral under a normal channel post so the Thread panel can host it and the follow-up ephemeral.
const anchorMarker = `E2E mm_blocks anchor ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const ephemeralMarker = `E2E mm_blocks ephemeral action ${pw.random.id()}`;
const integrationUrl = `${testConfig.webhookInternalUrl}/mm_blocks_integration`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + external action)',
props: {
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'button',
text: 'Ping integration',
style: 'primary',
action_id: 'pw_mm_blocks_integration',
},
],
mm_blocks_actions: {
pw_mm_blocks_integration: {
type: 'external',
url: integrationUrl,
context: {},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
const replyOnAnchor = anchorPost.getByRole('button', {name: 'reply'});
await expect(replyOnAnchor).toBeVisible();
await replyOnAnchor.click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
// Thread RHS uses data-testid="rhsPostView", not "postView" (center channel only).
const ephemeralInThread = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker});
await expect(ephemeralInThread).toBeVisible();
await ephemeralInThread.getByRole('button', {name: 'Ping integration'}).click();
const integrationEphemeral = threadPanel
.getByTestId('rhsPostView')
.filter({hasText: /Playwright mm_blocks integration OK \(user:/});
await expect(integrationEphemeral).toBeVisible();
await expect(integrationEphemeral.getByText('(Only visible to you)', {exact: true})).toBeVisible();
},
);
test(
'ephemeral mm_blocks external action applies integration update in thread',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral', '@external_service']},
async ({pw, request}) => {
test.skip(
!(await isWebhookTestServerReachable(request)),
[
`Webhook test server is not reachable at ${testConfig.webhookBaseUrl}.`,
'Start it from the repo: cd e2e-tests/cypress && npm run start:webhook',
'Or set PW_WEBHOOK_BASE_URL when it runs elsewhere.',
].join('\n'),
);
await setupWebhookTestServer(request, {
mattermostBaseUrl: testConfig.internalBaseURL,
adminUsername: testConfig.adminUsername,
adminPassword: testConfig.adminPassword,
});
const {team, user, adminClient, userClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const anchorMarker = `E2E mm_blocks anchor ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const ephemeralMarker = `E2E mm_blocks ephemeral update ${pw.random.id()}`;
const integrationUrl = `${testConfig.webhookInternalUrl}/mm_blocks_integration_update`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + apply update)',
props: {
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'button',
text: 'Apply update',
style: 'primary',
action_id: 'pw_mm_blocks_apply_update',
},
],
mm_blocks_actions: {
pw_mm_blocks_apply_update: {
type: 'external',
url: integrationUrl,
context: {},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
const replyOnAnchor = anchorPost.getByRole('button', {name: 'reply'});
await expect(replyOnAnchor).toBeVisible();
await replyOnAnchor.click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
const ephemeralInThread = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker});
await expect(ephemeralInThread).toBeVisible();
await ephemeralInThread.getByRole('button', {name: 'Apply update'}).click();
await expect(
threadPanel.getByTestId('rhsPostView').filter({hasText: 'PLAYWRIGHT_MM_BLOCKS_UPDATED'}),
).toBeVisible();
},
);
test(
'ephemeral mm_blocks username override persists after integration update in thread',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral', '@external_service']},
async ({pw, request}) => {
test.skip(
!(await isWebhookTestServerReachable(request)),
[
`Webhook test server is not reachable at ${testConfig.webhookBaseUrl}.`,
'Start it from the repo: cd e2e-tests/cypress && npm run start:webhook',
'Or set PW_WEBHOOK_BASE_URL when it runs elsewhere.',
].join('\n'),
);
await setupWebhookTestServer(request, {
mattermostBaseUrl: testConfig.internalBaseURL,
adminUsername: testConfig.adminUsername,
adminPassword: testConfig.adminPassword,
});
const {team, user, adminClient, userClient} = await pw.initSetup();
await adminClient.patchConfig({ServiceSettings: {EnablePostUsernameOverride: true}});
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const anchorMarker = `E2E mm_blocks anchor override ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const overrideAuthorName = 'Playwright mm_blocks eph override';
const ephemeralMarker = `E2E mm_blocks ephemeral override ${pw.random.id()}`;
const integrationUrl = `${testConfig.webhookInternalUrl}/mm_blocks_integration_update`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + override + apply update)',
props: {
from_webhook: 'true',
override_username: overrideAuthorName,
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'button',
text: 'Apply update',
style: 'primary',
action_id: 'pw_mm_blocks_apply_update_eph_override',
},
],
mm_blocks_actions: {
pw_mm_blocks_apply_update_eph_override: {
type: 'external',
url: integrationUrl,
context: {},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
const replyOnAnchor = anchorPost.getByRole('button', {name: 'reply'});
await expect(replyOnAnchor).toBeVisible();
await replyOnAnchor.click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
const ephemeralInThread = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker});
await expect(ephemeralInThread).toBeVisible();
const author = ephemeralInThread.locator('.post__header .user-popover');
await expect(author).toContainText(overrideAuthorName);
await ephemeralInThread.getByRole('button', {name: 'Apply update'}).click();
const updated = threadPanel.getByTestId('rhsPostView').filter({hasText: 'PLAYWRIGHT_MM_BLOCKS_UPDATED'});
await expect(updated).toBeVisible();
await expect(updated.locator('.post__header .user-popover')).toContainText(overrideAuthorName);
},
);
test(
'ephemeral mm_blocks button merges mm_blocks_actions query with block query on integration URL',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral', '@external_service']},
async ({pw, request}) => {
test.skip(
!(await isWebhookTestServerReachable(request)),
[
`Webhook test server is not reachable at ${testConfig.webhookBaseUrl}.`,
'Start it from the repo: cd e2e-tests/cypress && npm run start:webhook',
'Or set PW_WEBHOOK_BASE_URL when it runs elsewhere.',
].join('\n'),
);
await setupWebhookTestServer(request, {
mattermostBaseUrl: testConfig.internalBaseURL,
adminUsername: testConfig.adminUsername,
adminPassword: testConfig.adminPassword,
});
const {team, user, adminClient, userClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const anchorMarker = `E2E mm_blocks eph query anchor ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const ephemeralMarker = `E2E mm_blocks eph query merge ${pw.random.id()}`;
const integrationUrl = `${testConfig.webhookInternalUrl}/mm_blocks_integration_echo_query`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + query merge)',
props: {
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'button',
text: 'Run query merge',
style: 'primary',
action_id: 'pw_mm_blocks_query_btn_eph',
query: {cli: 'from_block'},
},
],
mm_blocks_actions: {
pw_mm_blocks_query_btn_eph: {
type: 'external',
url: integrationUrl,
query: {srv: 'from_action'},
context: {},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
await anchorPost.getByRole('button', {name: 'reply'}).click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
const ephemeralInThread = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker});
await expect(ephemeralInThread).toBeVisible();
await ephemeralInThread.getByRole('button', {name: 'Run query merge'}).click();
const integrationEphemeral = threadPanel
.getByTestId('rhsPostView')
.filter({hasText: /Playwright mm_blocks query OK \(cli=from_block&srv=from_action\)/});
await expect(integrationEphemeral).toBeVisible();
await expect(integrationEphemeral.getByText('(Only visible to you)', {exact: true})).toBeVisible();
},
);
test(
'ephemeral mm_blocks button block query overrides duplicate mm_blocks_actions query keys',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral', '@external_service']},
async ({pw, request}) => {
test.skip(
!(await isWebhookTestServerReachable(request)),
[
`Webhook test server is not reachable at ${testConfig.webhookBaseUrl}.`,
'Start it from the repo: cd e2e-tests/cypress && npm run start:webhook',
'Or set PW_WEBHOOK_BASE_URL when it runs elsewhere.',
].join('\n'),
);
await setupWebhookTestServer(request, {
mattermostBaseUrl: testConfig.internalBaseURL,
adminUsername: testConfig.adminUsername,
adminPassword: testConfig.adminPassword,
});
const {team, user, adminClient, userClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const anchorMarker = `E2E mm_blocks eph override anchor ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const ephemeralMarker = `E2E mm_blocks eph query override ${pw.random.id()}`;
const integrationUrl = `${testConfig.webhookInternalUrl}/mm_blocks_integration_echo_query`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + query override)',
props: {
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'button',
text: 'Override dup key',
style: 'primary',
action_id: 'pw_mm_blocks_query_override_eph',
query: {dup: 'from_block'},
},
],
mm_blocks_actions: {
pw_mm_blocks_query_override_eph: {
type: 'external',
url: integrationUrl,
query: {dup: 'from_action'},
context: {},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
await anchorPost.getByRole('button', {name: 'reply'}).click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
const ephemeralInThread = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker});
await expect(ephemeralInThread).toBeVisible();
await ephemeralInThread.getByRole('button', {name: 'Override dup key'}).click();
await expect(threadPanel.getByText('Playwright mm_blocks query OK (dup=from_block)')).toBeVisible();
},
);
test(
'ephemeral mm_blocks static_select merges action and element query on integration URL',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral', '@external_service']},
async ({pw, request}) => {
test.skip(
!(await isWebhookTestServerReachable(request)),
[
`Webhook test server is not reachable at ${testConfig.webhookBaseUrl}.`,
'Start it from the repo: cd e2e-tests/cypress && npm run start:webhook',
'Or set PW_WEBHOOK_BASE_URL when it runs elsewhere.',
].join('\n'),
);
await setupWebhookTestServer(request, {
mattermostBaseUrl: testConfig.internalBaseURL,
adminUsername: testConfig.adminUsername,
adminPassword: testConfig.adminPassword,
});
const {team, user, adminClient, userClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const anchorMarker = `E2E mm_blocks eph select query anchor ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const ephemeralMarker = `E2E mm_blocks eph static_select query ${pw.random.id()}`;
const integrationUrl = `${testConfig.webhookInternalUrl}/mm_blocks_integration_echo_query`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + static_select query)',
props: {
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'static_select',
action_id: 'pw_mm_blocks_static_select_query_eph',
placeholder: 'Pick a region',
query: {cli: 'from_block'},
options: [
{text: 'North', value: 'opt_north'},
{text: 'South', value: 'opt_south'},
],
},
],
mm_blocks_actions: {
pw_mm_blocks_static_select_query_eph: {
type: 'external',
url: integrationUrl,
query: {srv: 'from_action'},
context: {},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
await anchorPost.getByRole('button', {name: 'reply'}).click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
const ephemeralRow = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker}).last();
await expect(ephemeralRow).toBeVisible();
const regionSelect = ephemeralRow.getByRole('combobox', {name: 'Pick a region'});
await expect(regionSelect).toBeVisible();
await regionSelect.click();
await regionSelect.fill('Nor');
await channelsPage.page.getByRole('option', {name: 'North'}).click();
const integrationEphemeral = threadPanel
.getByTestId('rhsPostView')
.filter({hasText: /Playwright mm_blocks query OK \(cli=from_block&srv=from_action\)/});
await expect(integrationEphemeral).toBeVisible();
await expect(integrationEphemeral.getByText('(Only visible to you)', {exact: true})).toBeVisible();
},
);
test(
'ephemeral mm_blocks static_select data_source users sends selected user id to integration',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral', '@external_service']},
async ({pw, request}) => {
test.skip(
!(await isWebhookTestServerReachable(request)),
[
`Webhook test server is not reachable at ${testConfig.webhookBaseUrl}.`,
'Start it from the repo: cd e2e-tests/cypress && npm run start:webhook',
'Or set PW_WEBHOOK_BASE_URL when it runs elsewhere.',
].join('\n'),
);
await setupWebhookTestServer(request, {
mattermostBaseUrl: testConfig.internalBaseURL,
adminUsername: testConfig.adminUsername,
adminPassword: testConfig.adminPassword,
});
const {team, user, adminClient, userClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const anchorMarker = `E2E mm_blocks eph ds users anchor ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const ephemeralMarker = `E2E mm_blocks eph static_select users ${pw.random.id()}`;
const integrationUrl = `${testConfig.webhookInternalUrl}/mm_blocks_integration_static_select`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + static_select users)',
props: {
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'static_select',
action_id: 'pw_mm_blocks_ds_users_eph',
placeholder: 'Pick a user',
data_source: 'users',
},
],
mm_blocks_actions: {
pw_mm_blocks_ds_users_eph: {
type: 'external',
url: integrationUrl,
context: {},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
await anchorPost.getByRole('button', {name: 'reply'}).click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
const ephemeralRow = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker}).last();
await expect(ephemeralRow).toBeVisible();
const userSelect = ephemeralRow.getByRole('combobox', {name: 'Pick a user'});
await expect(userSelect).toBeVisible();
await userSelect.click();
await userSelect.fill(user.username);
await channelsPage.page
.getByRole('option')
.filter({hasText: new RegExp(`@${user.username}\\b`)})
.first()
.click();
const integrationEphemeral = threadPanel.getByTestId('rhsPostView').filter({
hasText: new RegExp(`Playwright mm_blocks static_select OK \\(selected_option: ${user.id}\\)`),
});
await expect(integrationEphemeral).toBeVisible();
await expect(integrationEphemeral.getByText('(Only visible to you)', {exact: true})).toBeVisible();
},
);
test(
'ephemeral mm_blocks static_select data_source channels sends selected channel id to integration',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral', '@external_service']},
async ({pw, request}) => {
test.skip(
!(await isWebhookTestServerReachable(request)),
[
`Webhook test server is not reachable at ${testConfig.webhookBaseUrl}.`,
'Start it from the repo: cd e2e-tests/cypress && npm run start:webhook',
'Or set PW_WEBHOOK_BASE_URL when it runs elsewhere.',
].join('\n'),
);
await setupWebhookTestServer(request, {
mattermostBaseUrl: testConfig.internalBaseURL,
adminUsername: testConfig.adminUsername,
adminPassword: testConfig.adminPassword,
});
const {team, user, adminClient, userClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const anchorMarker = `E2E mm_blocks eph ds channels anchor ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const ephemeralMarker = `E2E mm_blocks eph static_select channels ${pw.random.id()}`;
const integrationUrl = `${testConfig.webhookInternalUrl}/mm_blocks_integration_static_select`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + static_select channels)',
props: {
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'static_select',
action_id: 'pw_mm_blocks_ds_channels_eph',
placeholder: 'Pick a channel',
data_source: 'channels',
},
],
mm_blocks_actions: {
pw_mm_blocks_ds_channels_eph: {
type: 'external',
url: integrationUrl,
context: {},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
await anchorPost.getByRole('button', {name: 'reply'}).click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
const ephemeralRow = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker}).last();
await expect(ephemeralRow).toBeVisible();
const channelSelect = ephemeralRow.getByRole('combobox', {name: 'Pick a channel'});
await expect(channelSelect).toBeVisible();
await channelSelect.click();
await channelSelect.fill('Town');
await channelsPage.page
.getByRole('option', {name: /Town Square/})
.first()
.click();
const integrationEphemeral = threadPanel.getByTestId('rhsPostView').filter({
hasText: new RegExp(`Playwright mm_blocks static_select OK \\(selected_option: ${townSquare.id}\\)`),
});
await expect(integrationEphemeral).toBeVisible();
await expect(integrationEphemeral.getByText('(Only visible to you)', {exact: true})).toBeVisible();
},
);
test(
'ephemeral mm_blocks button sends mm_blocks_actions context to integration',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral', '@external_service']},
async ({pw, request}) => {
test.skip(
!(await isWebhookTestServerReachable(request)),
[
`Webhook test server is not reachable at ${testConfig.webhookBaseUrl}.`,
'Start it from the repo: cd e2e-tests/cypress && npm run start:webhook',
'Or set PW_WEBHOOK_BASE_URL when it runs elsewhere.',
].join('\n'),
);
await setupWebhookTestServer(request, {
mattermostBaseUrl: testConfig.internalBaseURL,
adminUsername: testConfig.adminUsername,
adminPassword: testConfig.adminPassword,
});
const {team, user, adminClient, userClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
if (!townSquare) {
throw new Error('Town Square channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const anchorMarker = `E2E mm_blocks eph context anchor ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const contextMarker = `ctx_${pw.random.id()}`;
const ephemeralMarker = `E2E mm_blocks eph action_context ${pw.random.id()}`;
const integrationUrl = `${testConfig.webhookInternalUrl}/mm_blocks_integration_echo_context`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + action context)',
props: {
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'button',
text: 'Verify context',
style: 'primary',
action_id: 'pw_mm_blocks_echo_ctx_eph',
},
],
mm_blocks_actions: {
pw_mm_blocks_echo_ctx_eph: {
type: 'external',
url: integrationUrl,
context: {test_marker: contextMarker},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
await anchorPost.getByRole('button', {name: 'reply'}).click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
const ephemeralInThread = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker});
await expect(ephemeralInThread).toBeVisible();
await ephemeralInThread.getByRole('button', {name: 'Verify context'}).click();
const integrationEphemeral = threadPanel
.getByTestId('rhsPostView')
.filter({hasText: `Playwright mm_blocks context OK (test_marker: ${contextMarker})`});
await expect(integrationEphemeral).toBeVisible();
await expect(integrationEphemeral.getByText('(Only visible to you)', {exact: true})).toBeVisible();
},
);
test(
'ephemeral mm_blocks openURL action navigates with merged action query',
{tag: ['@interactive_messages', '@mm_blocks', '@ephemeral']},
async ({pw}) => {
const {team, user, adminClient, userClient} = await pw.initSetup();
const channels = await adminClient.getMyChannels(team.id);
const townSquare = channels.find((ch) => ch.name === 'town-square');
const offTopic = channels.find((ch) => ch.name === 'off-topic');
if (!townSquare || !offTopic) {
throw new Error('Town Square or Off-Topic channel not found');
}
const {channelsPage} = await pw.testBrowser.login(user);
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
const anchorMarker = `E2E mm_blocks eph openurl anchor ${pw.random.id()}`;
const anchor = await userClient.createPost({
channel_id: townSquare.id,
message: anchorMarker,
});
const ephemeralMarker = `E2E mm_blocks eph openurl ${pw.random.id()}`;
const targetChannelPath = `/${team.name}/channels/off-topic`;
await adminClient.createPostEphemeral(user.id, {
channel_id: townSquare.id,
root_id: anchor.id,
message: 'E2E mm_blocks (ephemeral + openURL)',
props: {
mm_blocks: [
{type: 'text', text: ephemeralMarker},
{
type: 'button',
text: 'Go to Off-Topic',
style: 'primary',
action_id: 'pw_mm_blocks_openurl_eph',
},
],
mm_blocks_actions: {
pw_mm_blocks_openurl_eph: {
type: 'openURL',
url: targetChannelPath,
query: {mm_openurl: 'from_action_eph'},
},
},
},
});
const lastPost = await channelsPage.getLastPost();
await lastPost.toBeVisible();
const anchorPost = lastPost.container;
await anchorPost.hover();
await anchorPost.getByRole('button', {name: 'reply'}).click();
const threadPanel = channelsPage.page.getByRole('region', {name: /Thread/});
await expect(threadPanel).toBeVisible();
const ephemeralInThread = threadPanel.getByTestId('rhsPostView').filter({hasText: ephemeralMarker});
await expect(ephemeralInThread).toBeVisible();
await ephemeralInThread.getByRole('button', {name: 'Go to Off-Topic'}).click();
await expect(channelsPage.page).toHaveURL(/\/channels\/off-topic/);
expect(new URL(channelsPage.page.url()).searchParams.get('mm_openurl')).toBe('from_action_eph');
},
);
});