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 Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
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 Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
import {BOR_TAG, setupBorTest, createSecondUser} from './support';
|
||||
|
||||
test.describe('Burn-on-Read in DMs and GMs', () => {
|
||||
test('MM-66742_1 BoR toggle is available in DM channel', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create second user for DM
|
||||
const otherUser = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM channel
|
||||
await adminClient.createDirectChannel([user.id, otherUser.id]);
|
||||
|
||||
// # Login and navigate to DM
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name);
|
||||
await channelsPage.toBeVisible();
|
||||
await channelsPage.goto(team.name, `@${otherUser.username}`);
|
||||
|
||||
// * Verify BoR toggle button is available
|
||||
await expect(channelsPage.centerView.postCreate.burnOnReadButton).toBeAttached();
|
||||
await expect(channelsPage.centerView.postCreate.burnOnReadButton).toBeEnabled();
|
||||
|
||||
// # Toggle BoR on
|
||||
await channelsPage.centerView.postCreate.toggleBurnOnRead();
|
||||
|
||||
// * Verify BoR label appears indicating it's enabled
|
||||
await expect(channelsPage.centerView.postCreate.burnOnReadLabel).toBeVisible();
|
||||
});
|
||||
|
||||
test('MM-66742_2 complete BoR flow in DM between two users', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM channel
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and navigate to DM
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
|
||||
// # Enable BoR and send message
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const secretMessage = `DM secret ${pw.random.id()}`;
|
||||
await senderPage.postMessage(secretMessage);
|
||||
|
||||
// # Get sender's view of the post
|
||||
const senderPost = await senderPage.getLastPost();
|
||||
|
||||
// * Verify sender sees the message content (not concealed)
|
||||
await expect(senderPost.body).toContainText(secretMessage);
|
||||
|
||||
// * Verify sender sees flame badge
|
||||
await expect(senderPost.burnOnReadBadge.container).toBeVisible();
|
||||
|
||||
// # Login as receiver and navigate to DM
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name);
|
||||
await receiverPage.toBeVisible();
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
|
||||
// # Get receiver's view of the post
|
||||
const receiverPost = await receiverPage.getLastPost();
|
||||
|
||||
// * Verify receiver sees concealed placeholder
|
||||
await expect(receiverPost.concealedPlaceholder.container).toBeVisible();
|
||||
|
||||
// * Verify receiver does NOT see the message content
|
||||
await expect(receiverPost.body).not.toContainText(secretMessage);
|
||||
|
||||
// # Reveal the message
|
||||
await receiverPost.concealedPlaceholder.clickToReveal();
|
||||
await receiverPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Verify receiver now sees the message content
|
||||
await expect(receiverPost.body).toContainText(secretMessage);
|
||||
|
||||
// * Verify timer chip appears (wait for WebSocket update)
|
||||
await expect(receiverPost.burnOnReadTimerChip.container).toBeVisible({timeout: 15000});
|
||||
});
|
||||
|
||||
test(
|
||||
'MM-66742_3 BoR message in group message with multiple recipients',
|
||||
{tag: [BOR_TAG]},
|
||||
async ({pw}, testInfo) => {
|
||||
testInfo.setTimeout(120000);
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create two other users for the group
|
||||
const recipient1 = await createSecondUser(pw, adminClient, team);
|
||||
const recipient2 = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create a private channel with exactly 3 members (sender + 2 recipients)
|
||||
// This gives us control over the exact member count
|
||||
const channelSuffix = Date.now().toString(36);
|
||||
const channel = await adminClient.createChannel(
|
||||
pw.random.channel({
|
||||
teamId: team.id,
|
||||
name: `bor-gm-test-${channelSuffix}`,
|
||||
displayName: `BoR GM Test ${channelSuffix}`,
|
||||
type: 'P', // Private channel
|
||||
}),
|
||||
);
|
||||
|
||||
// Add all test users to the channel
|
||||
await adminClient.addToChannel(sender.id, channel.id);
|
||||
await adminClient.addToChannel(recipient1.id, channel.id);
|
||||
await adminClient.addToChannel(recipient2.id, channel.id);
|
||||
|
||||
// Remove admin from channel (auto-added as creator)
|
||||
const adminUser = await adminClient.getMe();
|
||||
await adminClient.removeFromChannel(adminUser.id, channel.id);
|
||||
|
||||
// # Login as sender and navigate to the channel
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, channel.name);
|
||||
await senderPage.toBeVisible();
|
||||
|
||||
// # Enable BoR and send message
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const secretMessage = `GM secret ${pw.random.id()}`;
|
||||
await senderPage.postMessage(secretMessage);
|
||||
|
||||
// # Get sender's view
|
||||
const senderPost = await senderPage.getLastPost();
|
||||
|
||||
// * Verify sender sees flame badge
|
||||
await expect(senderPost.burnOnReadBadge.container).toBeVisible();
|
||||
|
||||
// * Verify tooltip shows 0 of 2 recipients (channel has 3 members - 1 sender = 2 recipients)
|
||||
const tooltipText = await senderPost.burnOnReadBadge.getTooltipText();
|
||||
expect(tooltipText).toContain('Read by 0 of 2');
|
||||
|
||||
// # Login as first recipient and reveal
|
||||
const {channelsPage: recipient1Page} = await pw.testBrowser.login(recipient1);
|
||||
await recipient1Page.goto(team.name, channel.name);
|
||||
await recipient1Page.toBeVisible();
|
||||
|
||||
const recipient1Post = await recipient1Page.getLastPost();
|
||||
await recipient1Post.concealedPlaceholder.clickToReveal();
|
||||
await recipient1Post.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Verify first recipient sees message and timer (wait for WebSocket update)
|
||||
await expect(recipient1Post.body).toContainText(secretMessage);
|
||||
await expect(recipient1Post.burnOnReadTimerChip.container).toBeVisible({timeout: 15000});
|
||||
|
||||
// # Refresh sender and verify updated count
|
||||
await senderPage.page.reload();
|
||||
await senderPage.toBeVisible();
|
||||
const updatedSenderPost = await senderPage.getLastPost();
|
||||
await updatedSenderPost.burnOnReadBadge.hover();
|
||||
const updatedTooltip = await updatedSenderPost.burnOnReadBadge.getTooltipText();
|
||||
expect(updatedTooltip).toContain('Read by 1 of 2');
|
||||
|
||||
// # Login as second recipient and reveal
|
||||
const {channelsPage: recipient2Page} = await pw.testBrowser.login(recipient2);
|
||||
await recipient2Page.goto(team.name, channel.name);
|
||||
await recipient2Page.toBeVisible();
|
||||
|
||||
const recipient2Post = await recipient2Page.getLastPost();
|
||||
await recipient2Post.concealedPlaceholder.clickToReveal();
|
||||
await recipient2Post.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Verify second recipient sees message and timer (wait for WebSocket update)
|
||||
await expect(recipient2Post.body).toContainText(secretMessage);
|
||||
await expect(recipient2Post.burnOnReadTimerChip.container).toBeVisible({timeout: 15000});
|
||||
},
|
||||
);
|
||||
|
||||
test('MM-66742_4 sender deletes BoR in DM and recipient cannot see it', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM channel
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and navigate to DM
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
|
||||
// # Enable BoR and send message
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const secretMessage = `To be deleted ${pw.random.id()}`;
|
||||
await senderPage.postMessage(secretMessage);
|
||||
|
||||
// # Get the post ID
|
||||
const senderPost = await senderPage.getLastPost();
|
||||
const postId = await senderPost.getId();
|
||||
|
||||
// # Sender clicks flame badge to delete
|
||||
await senderPost.burnOnReadBadge.click();
|
||||
|
||||
// * Verify confirmation modal appears
|
||||
await expect(senderPage.burnOnReadConfirmationModal.container).toBeVisible();
|
||||
|
||||
// # Confirm deletion
|
||||
await senderPage.burnOnReadConfirmationModal.confirm();
|
||||
|
||||
// * Verify post is removed from sender's view
|
||||
const deletedPostSender = senderPage.page.locator(`[id="post_${postId}"]`);
|
||||
await expect(deletedPostSender).not.toBeVisible();
|
||||
|
||||
// # Login as receiver and verify message is not there
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name);
|
||||
await receiverPage.toBeVisible();
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
|
||||
// * Verify the deleted message is not visible to receiver
|
||||
const deletedPostReceiver = receiverPage.page.locator(`[id="post_${postId}"]`);
|
||||
await expect(deletedPostReceiver).not.toBeVisible();
|
||||
|
||||
// * Verify message content is not in the channel
|
||||
const channelContent = await receiverPage.centerView.container.textContent();
|
||||
expect(channelContent).not.toContain(secretMessage);
|
||||
});
|
||||
|
||||
test('MM-66742_5 receiver burns revealed BoR in DM via timer chip', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM channel
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and navigate to DM
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
|
||||
// # Enable BoR and send message
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const secretMessage = `Receiver will burn ${pw.random.id()}`;
|
||||
await senderPage.postMessage(secretMessage);
|
||||
|
||||
// # Login as receiver
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
// # Reveal the message
|
||||
const receiverPost = await receiverPage.getLastPost();
|
||||
const postId = await receiverPost.getId();
|
||||
await receiverPost.concealedPlaceholder.clickToReveal();
|
||||
await receiverPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Verify message is revealed
|
||||
await expect(receiverPost.body).toContainText(secretMessage);
|
||||
|
||||
// * Wait for timer chip to appear (WebSocket update)
|
||||
await expect(receiverPost.burnOnReadTimerChip.container).toBeVisible({timeout: 15000});
|
||||
|
||||
// # Click timer to burn
|
||||
await receiverPost.burnOnReadTimerChip.click();
|
||||
|
||||
// * Verify confirmation modal
|
||||
await expect(receiverPage.burnOnReadConfirmationModal.container).toBeVisible();
|
||||
|
||||
// # Confirm
|
||||
await receiverPage.burnOnReadConfirmationModal.confirm();
|
||||
|
||||
// * Verify post is removed from receiver's view
|
||||
const deletedPostReceiver = receiverPage.page.locator(`[id="post_${postId}"]`);
|
||||
await expect(deletedPostReceiver).not.toBeVisible({timeout: 15000});
|
||||
|
||||
// * Verify message content is not visible in the channel
|
||||
await expect(receiverPage.centerView.container).not.toContainText(secretMessage);
|
||||
});
|
||||
|
||||
test('MM-66742_6 DM shows correct recipient count of 1', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM channel
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and navigate to DM
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
|
||||
// # Enable BoR and send message
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const secretMessage = `Count test ${pw.random.id()}`;
|
||||
await senderPage.postMessage(secretMessage);
|
||||
|
||||
// # Get sender's view
|
||||
const senderPost = await senderPage.getLastPost();
|
||||
|
||||
// * Verify tooltip shows exactly 1 recipient
|
||||
await senderPost.burnOnReadBadge.hover();
|
||||
const recipientCount = await senderPost.burnOnReadBadge.getRecipientCount();
|
||||
expect(recipientCount.total).toBe(1);
|
||||
expect(recipientCount.revealed).toBe(0);
|
||||
});
|
||||
|
||||
test('MM-66742_7 multiple BoR messages in same DM conversation', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM channel
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
|
||||
// # Send first BoR message
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message1 = `First BoR ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message1);
|
||||
|
||||
// # Send second BoR message (toggle again to ensure BoR is on)
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message2 = `Second BoR ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message2);
|
||||
|
||||
// # Login as receiver and verify they can see concealed messages
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
// * Verify concealed BoR placeholders are visible
|
||||
await expect(receiverPage.centerView.container.getByTestId(/^burn-on-read-concealed-/).first()).toBeVisible();
|
||||
|
||||
// * Get all concealed placeholders
|
||||
const concealedPlaceholders = receiverPage.centerView.container.locator('.BurnOnReadConcealedPlaceholder');
|
||||
const count = await concealedPlaceholders.count();
|
||||
expect(count).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// # Reveal all concealed messages
|
||||
for (let i = 0; i < count; i++) {
|
||||
const placeholder = concealedPlaceholders.nth(i);
|
||||
if (await placeholder.isVisible()) {
|
||||
await placeholder.click();
|
||||
// Wait for reveal animation
|
||||
await receiverPage.page.waitForTimeout(500);
|
||||
}
|
||||
}
|
||||
|
||||
// * Verify at least one of the messages is visible after revealing
|
||||
const pageContent = await receiverPage.centerView.container.textContent();
|
||||
const hasMessage1 = pageContent?.includes(message1);
|
||||
const hasMessage2 = pageContent?.includes(message2);
|
||||
expect(hasMessage1 || hasMessage2).toBe(true);
|
||||
});
|
||||
|
||||
test('MM-66742_8 BoR toggle resets after sending message', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM channel
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
|
||||
// # Enable BoR toggle
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
|
||||
// * Verify BoR is enabled
|
||||
const isEnabledBefore = await senderPage.centerView.postCreate.isBurnOnReadEnabled();
|
||||
expect(isEnabledBefore).toBe(true);
|
||||
|
||||
// # Send a message
|
||||
const message1 = `BoR message ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message1);
|
||||
|
||||
// * Verify BoR is disabled after sending (toggle resets)
|
||||
const isEnabledAfter = await senderPage.centerView.postCreate.isBurnOnReadEnabled();
|
||||
expect(isEnabledAfter).toBe(false);
|
||||
|
||||
// * Verify the sent message has BoR badge (was sent as BoR)
|
||||
const lastPost = await senderPage.getLastPost();
|
||||
await expect(lastPost.burnOnReadBadge.container).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,355 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
import {BOR_TAG, setupBorTest, createSecondUser} from './support';
|
||||
|
||||
test.describe('Burn-on-Read Receiver Flow', () => {
|
||||
test('MM-66742_9 receiver sees concealed placeholder and reveals message', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create a DM channel between sender and receiver for controlled environment
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and navigate to DM
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
|
||||
// # Post BoR message
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const secretMessage = `Secret message ${pw.random.id()}`;
|
||||
await senderPage.postMessage(secretMessage);
|
||||
|
||||
// # Login as receiver in new context
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
// # Get the BoR post
|
||||
const borPost = await receiverPage.getLastPost();
|
||||
|
||||
// * Verify post is concealed (placeholder shown)
|
||||
await expect(borPost.concealedPlaceholder.container).toBeVisible();
|
||||
|
||||
// * Verify the actual message is NOT visible
|
||||
await expect(borPost.body).not.toContainText(secretMessage);
|
||||
|
||||
// * Verify concealed placeholder has correct text
|
||||
const placeholderText = await borPost.concealedPlaceholder.getText();
|
||||
expect(placeholderText).toContain('View message');
|
||||
|
||||
// # Click to reveal
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
|
||||
// * Wait for reveal to complete
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Verify message is now visible
|
||||
await expect(borPost.body).toContainText(secretMessage);
|
||||
|
||||
// * Verify concealed placeholder is no longer visible
|
||||
await expect(borPost.concealedPlaceholder.container).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('MM-66742_10 receiver manually burns revealed message via timer chip', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM for controlled environment
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and post BoR message
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `To be burned ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
// # Login as receiver
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
// # Get the BoR post and reveal it
|
||||
const borPost = await receiverPage.getLastPost();
|
||||
const postId = await borPost.getId();
|
||||
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Verify message is revealed
|
||||
await expect(borPost.body).toContainText(message);
|
||||
|
||||
// * Wait for timer chip to appear (WebSocket update)
|
||||
await expect(borPost.burnOnReadTimerChip.container).toBeVisible({timeout: 15000});
|
||||
|
||||
// # Click timer chip to manually burn
|
||||
await borPost.burnOnReadTimerChip.click();
|
||||
|
||||
// * Verify confirmation modal appears
|
||||
await expect(receiverPage.burnOnReadConfirmationModal.container).toBeVisible();
|
||||
|
||||
// # Confirm deletion
|
||||
await receiverPage.burnOnReadConfirmationModal.confirm();
|
||||
|
||||
// * Verify post is removed from receiver's view
|
||||
const deletedPostLocator = receiverPage.page.locator(`[id="post_${postId}"]`);
|
||||
await expect(deletedPostLocator).not.toBeVisible();
|
||||
});
|
||||
|
||||
test(
|
||||
'MM-66742_11 receiver uses dont show again preference for burn confirmation',
|
||||
{tag: [BOR_TAG]},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM for controlled environment
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and post BoR message
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `Test message ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
// # Login as receiver
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
// # Reveal the message
|
||||
const borPost = await receiverPage.getLastPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Wait for timer chip to be visible
|
||||
await expect(borPost.burnOnReadTimerChip.container).toBeVisible({timeout: 15000});
|
||||
|
||||
// # Click timer to burn
|
||||
await borPost.burnOnReadTimerChip.click();
|
||||
|
||||
// * Verify confirmation modal appears
|
||||
await expect(receiverPage.burnOnReadConfirmationModal.container).toBeVisible();
|
||||
|
||||
// * Verify "don't show again" checkbox is available
|
||||
await expect(receiverPage.burnOnReadConfirmationModal.dontShowAgainCheckbox).toBeVisible();
|
||||
|
||||
// # Check "don't show again" and confirm (combined action)
|
||||
await receiverPage.burnOnReadConfirmationModal.confirmWithDontShowAgain();
|
||||
|
||||
// * Verify post is deleted
|
||||
await expect(borPost.container).not.toBeVisible({timeout: 10000});
|
||||
},
|
||||
);
|
||||
|
||||
test('MM-66742_12 timer chip displays countdown after reveal', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with 60 second duration
|
||||
const {
|
||||
user: sender,
|
||||
team,
|
||||
adminClient,
|
||||
} = await setupBorTest(pw, {
|
||||
durationSeconds: 60,
|
||||
});
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM for controlled environment
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and post BoR message
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `Timer test ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
// # Login as receiver
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
// # Get the BoR post and reveal it
|
||||
const borPost = await receiverPage.getLastPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Verify timer chip is visible (wait for WebSocket update)
|
||||
await expect(borPost.burnOnReadTimerChip.container).toBeVisible({timeout: 15000});
|
||||
|
||||
// * Get initial time
|
||||
const initialTime = await borPost.burnOnReadTimerChip.getTimeRemaining();
|
||||
|
||||
// * Verify time format is correct (MM:SS or M:SS)
|
||||
expect(initialTime).toMatch(/^\d+:\d{2}$/);
|
||||
|
||||
// # Wait 2 seconds
|
||||
await receiverPage.page.waitForTimeout(2000);
|
||||
|
||||
// * Get updated time
|
||||
const updatedTime = await borPost.burnOnReadTimerChip.getTimeRemaining();
|
||||
|
||||
// * Verify time has decreased
|
||||
expect(updatedTime).toMatch(/^\d+:\d{2}$/);
|
||||
// Parse times to compare
|
||||
const parseTime = (t: string) => {
|
||||
const [m, s] = t.split(':').map(Number);
|
||||
return m * 60 + s;
|
||||
};
|
||||
expect(parseTime(updatedTime)).toBeLessThan(parseTime(initialTime));
|
||||
});
|
||||
|
||||
test('MM-66742_13 message auto-deletes after timer expires', {tag: [BOR_TAG]}, async ({pw}, testInfo) => {
|
||||
testInfo.setTimeout(120000);
|
||||
// # Initialize setup with very short duration (10 seconds)
|
||||
const {
|
||||
user: sender,
|
||||
team,
|
||||
adminClient,
|
||||
} = await setupBorTest(pw, {
|
||||
durationSeconds: 10,
|
||||
maxTTLSeconds: 86400,
|
||||
});
|
||||
|
||||
// # Verify the config was applied before proceeding (guard against state pollution)
|
||||
await pw.waitUntil(async () => {
|
||||
const cfg = await adminClient.getConfig();
|
||||
return cfg.ServiceSettings.BurnOnReadDurationSeconds === 10;
|
||||
});
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM for controlled environment
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and post BoR message
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
// Re-apply guard: concurrent initSetup() may reset BurnOnReadDurationSeconds to 60
|
||||
// (default) after the pw.waitUntil check above but before the message is posted.
|
||||
await adminClient.patchConfig({
|
||||
ServiceSettings: {
|
||||
EnableBurnOnRead: true,
|
||||
BurnOnReadDurationSeconds: 10,
|
||||
BurnOnReadMaximumTimeToLiveSeconds: 86400,
|
||||
},
|
||||
});
|
||||
// Confirm the re-apply actually took effect before the post is created.
|
||||
await pw.waitUntil(async () => {
|
||||
const cfg = await adminClient.getConfig();
|
||||
return cfg.ServiceSettings.BurnOnReadDurationSeconds === 10;
|
||||
});
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `Auto-delete test ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
// # Login as receiver
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
// # Get the BoR post and reveal it
|
||||
const borPost = await receiverPage.getLastPost();
|
||||
const postId = await borPost.getId();
|
||||
|
||||
// Re-apply guard: TTL is assigned by the server at reveal time, not post time.
|
||||
// A concurrent initSetup() may have reset BurnOnReadDurationSeconds to its
|
||||
// default (60 s) between the sender's post and the receiver's reveal click.
|
||||
// Re-applying here ensures the server uses 10 s when it writes the TTL.
|
||||
await adminClient.patchConfig({
|
||||
ServiceSettings: {
|
||||
EnableBurnOnRead: true,
|
||||
BurnOnReadDurationSeconds: 10,
|
||||
BurnOnReadMaximumTimeToLiveSeconds: 86400,
|
||||
},
|
||||
});
|
||||
await pw.waitUntil(async () => {
|
||||
const cfg = await adminClient.getConfig();
|
||||
return cfg.ServiceSettings.BurnOnReadDurationSeconds === 10;
|
||||
});
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Verify message is visible and timer is running (wait for WebSocket update)
|
||||
await expect(borPost.body).toContainText(message);
|
||||
await expect(borPost.burnOnReadTimerChip.container).toBeVisible({timeout: 15000});
|
||||
|
||||
// # Wait for timer to expire (10 seconds + buffer)
|
||||
// Use polling to check for post removal
|
||||
await expect(async () => {
|
||||
const postLocator = receiverPage.page.locator(`[id="post_${postId}"]`);
|
||||
await expect(postLocator).not.toBeVisible();
|
||||
}).toPass({
|
||||
timeout: 30000,
|
||||
intervals: [1000],
|
||||
});
|
||||
|
||||
// * Verify message is no longer visible
|
||||
const deletedPostLocator = receiverPage.page.locator(`[id="post_${postId}"]`);
|
||||
await expect(deletedPostLocator).not.toBeVisible();
|
||||
|
||||
// * Verify message text is not in the channel
|
||||
const pageContent = await receiverPage.centerView.container.textContent();
|
||||
expect(pageContent).not.toContain(message);
|
||||
});
|
||||
|
||||
test('MM-66742_14 receiver sees flame badge on concealed message', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create receiver
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create DM for controlled environment
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
// # Login as sender and post BoR message
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `Badge test ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
// # Login as receiver
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name);
|
||||
await receiverPage.toBeVisible();
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
|
||||
// # Get the BoR post (still concealed)
|
||||
const borPost = await receiverPage.getLastPost();
|
||||
|
||||
// * Verify concealed placeholder is visible
|
||||
await expect(borPost.concealedPlaceholder.container).toBeVisible();
|
||||
|
||||
// * Verify flame badge is also visible on concealed post
|
||||
await expect(borPost.burnOnReadBadge.container).toBeVisible();
|
||||
|
||||
// * Verify badge tooltip shows appropriate message for receiver
|
||||
const ariaLabel = await borPost.burnOnReadBadge.getAriaLabel();
|
||||
expect(ariaLabel).toContain('Burn-on-read message');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,283 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
import {BOR_TAG, setupBorTest, createSecondUser} from './support';
|
||||
|
||||
test.describe('Burn-on-Read Restrictions', () => {
|
||||
test('MM-66742_19 no reply option in dot menu for BoR post', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
await senderPage.postMessage(`No reply test ${pw.random.id()}`);
|
||||
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
const borPost = await receiverPage.centerView.getConcealedBorPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// # Open dot menu via page object
|
||||
await borPost.hover();
|
||||
await borPost.postMenu.openDotMenu();
|
||||
|
||||
// * Verify Reply option is NOT present
|
||||
await expect(receiverPage.postDotMenu.replyMenuItem).not.toBeVisible();
|
||||
|
||||
await receiverPage.page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('MM-66742_20 no pin option in dot menu for BoR post', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
await senderPage.postMessage(`No pin test ${pw.random.id()}`);
|
||||
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
const borPost = await receiverPage.centerView.getConcealedBorPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
await borPost.hover();
|
||||
await borPost.postMenu.openDotMenu();
|
||||
|
||||
// * Verify Pin option is NOT present
|
||||
await expect(receiverPage.postDotMenu.pinToChannelMenuItem).not.toBeVisible();
|
||||
|
||||
await receiverPage.page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('MM-66742_21 no edit option in dot menu for BoR post (sender)', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `No edit test ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
const borPost = await senderPage.centerView.getPostByText(message);
|
||||
|
||||
await borPost.hover();
|
||||
await borPost.postMenu.openDotMenu();
|
||||
|
||||
// * Verify Edit option is NOT present (even for sender)
|
||||
await expect(senderPage.postDotMenu.editMenuItem).not.toBeVisible();
|
||||
|
||||
await senderPage.page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('MM-66742_22 no forward option in dot menu for BoR post', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
await senderPage.postMessage(`No forward test ${pw.random.id()}`);
|
||||
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
const borPost = await receiverPage.centerView.getConcealedBorPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
await borPost.hover();
|
||||
await borPost.postMenu.openDotMenu();
|
||||
|
||||
// * Verify Forward option is NOT present
|
||||
await expect(receiverPage.postDotMenu.forwardMenuItem).not.toBeVisible();
|
||||
|
||||
await receiverPage.page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('MM-66742_23 no copy text option in dot menu for BoR post (receiver)', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
await senderPage.postMessage(`No copy test ${pw.random.id()}`);
|
||||
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
const borPost = await receiverPage.centerView.getConcealedBorPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
await borPost.hover();
|
||||
await borPost.postMenu.openDotMenu();
|
||||
|
||||
// * Verify Copy Text option is NOT present for receiver
|
||||
await expect(receiverPage.postDotMenu.copyTextMenuItem).not.toBeVisible();
|
||||
|
||||
await receiverPage.page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('MM-66742_24 no copy link option for receiver of BoR post', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
await senderPage.postMessage(`No copy link test ${pw.random.id()}`);
|
||||
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
const borPost = await receiverPage.centerView.getConcealedBorPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
await borPost.hover();
|
||||
await borPost.postMenu.openDotMenu();
|
||||
|
||||
// * Verify Copy Link option is NOT present for receiver
|
||||
await expect(receiverPage.postDotMenu.copyLinkMenuItem).not.toBeVisible();
|
||||
|
||||
await receiverPage.page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('MM-66742_25 sender can copy link to own BoR post', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `Sender copy link test ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
const borPost = await senderPage.centerView.getPostByText(message);
|
||||
|
||||
await borPost.hover();
|
||||
await borPost.postMenu.openDotMenu();
|
||||
|
||||
// * Verify Copy Link option IS present for sender
|
||||
await expect(senderPage.postDotMenu.copyLinkMenuItem).toBeVisible();
|
||||
|
||||
await senderPage.page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('MM-66742_26 no follow thread option for BoR post', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
await senderPage.postMessage(`No follow test ${pw.random.id()}`);
|
||||
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
const borPost = await receiverPage.centerView.getConcealedBorPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
await borPost.hover();
|
||||
await borPost.postMenu.openDotMenu();
|
||||
|
||||
// * Verify Follow Thread option is NOT present
|
||||
await expect(receiverPage.postDotMenu.followMessageMenuItem).not.toBeVisible();
|
||||
|
||||
await receiverPage.page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('MM-66742_27 keyboard shortcut Shift+UP does not open reply for BoR post', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `Keyboard test ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
const borPost = await receiverPage.centerView.getConcealedBorPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// # Focus on the message input box and press Shift+UP
|
||||
await receiverPage.centerView.postCreate.input.click();
|
||||
await receiverPage.page.keyboard.press('Shift+ArrowUp');
|
||||
|
||||
// * Verify RHS does not open with the BoR message
|
||||
await expect(receiverPage.sidebarRight.container)
|
||||
.toBeHidden({timeout: 2000})
|
||||
.catch(async () => {
|
||||
await expect(receiverPage.sidebarRight.container).not.toContainText(message);
|
||||
});
|
||||
});
|
||||
|
||||
test('MM-66742_28 delete option available for revealed BoR post', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
const {user: sender, team, adminClient} = await setupBorTest(pw);
|
||||
const receiver = await createSecondUser(pw, adminClient, team);
|
||||
await adminClient.createDirectChannel([sender.id, receiver.id]);
|
||||
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(sender);
|
||||
await senderPage.goto(team.name, `@${receiver.username}`);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
await senderPage.postMessage(`Delete test ${pw.random.id()}`);
|
||||
|
||||
const {channelsPage: receiverPage} = await pw.testBrowser.login(receiver);
|
||||
await receiverPage.goto(team.name, `@${sender.username}`);
|
||||
await receiverPage.toBeVisible();
|
||||
|
||||
const borPost = await receiverPage.centerView.getConcealedBorPost();
|
||||
await borPost.concealedPlaceholder.clickToReveal();
|
||||
await borPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
await borPost.hover();
|
||||
await borPost.postMenu.openDotMenu();
|
||||
|
||||
// * Verify Delete option IS present
|
||||
await expect(receiverPage.postDotMenu.deleteMenuItem).toBeVisible();
|
||||
|
||||
await receiverPage.page.keyboard.press('Escape');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
import {BOR_TAG, setupBorTest, createSecondUser} from './support';
|
||||
|
||||
test.describe('Burn-on-Read Sender Flow', () => {
|
||||
test('MM-66742_15 sends BoR message and views sent status with recipient count', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create second user as recipient (needed for BoR badge count)
|
||||
await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Login as sender
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// # Enable BoR toggle in composer
|
||||
await channelsPage.centerView.postCreate.toggleBurnOnRead();
|
||||
|
||||
// * Verify BoR label appears
|
||||
await expect(channelsPage.centerView.postCreate.burnOnReadLabel).toBeVisible();
|
||||
|
||||
// # Send BoR message
|
||||
const message = `BoR Test ${pw.random.id()}`;
|
||||
await channelsPage.postMessage(message);
|
||||
|
||||
// # Get the posted message
|
||||
const lastPost = await channelsPage.getLastPost();
|
||||
|
||||
// * Verify post has BoR badge
|
||||
await expect(lastPost.burnOnReadBadge.container).toBeVisible();
|
||||
|
||||
// * Verify flame icon is displayed
|
||||
await expect(lastPost.burnOnReadBadge.flameIcon).toBeVisible();
|
||||
|
||||
// # Hover over badge to see tooltip
|
||||
await lastPost.burnOnReadBadge.hover();
|
||||
|
||||
// * Verify tooltip shows recipient info
|
||||
const tooltipText = await lastPost.burnOnReadBadge.getTooltipText();
|
||||
expect(tooltipText).toContain('Read by 0 of');
|
||||
expect(tooltipText).toContain('Click to delete');
|
||||
});
|
||||
|
||||
test('MM-66742_16 sender sees read receipts in tooltip', {tag: [BOR_TAG]}, async ({pw}, testInfo) => {
|
||||
testInfo.setTimeout(120000);
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create two recipients
|
||||
const recipient1 = await createSecondUser(pw, adminClient, team);
|
||||
const recipient2 = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create a private channel with exactly these 3 users (sender + 2 recipients)
|
||||
// Use timestamp to ensure unique channel name across test runs
|
||||
const channelSuffix = Date.now().toString(36);
|
||||
const channel = await adminClient.createChannel(
|
||||
pw.random.channel({
|
||||
teamId: team.id,
|
||||
name: `bor-test-${channelSuffix}`,
|
||||
displayName: `BoR Test ${channelSuffix}`,
|
||||
type: 'P', // Private channel
|
||||
}),
|
||||
);
|
||||
|
||||
// Add all test users to the channel
|
||||
await adminClient.addToChannel(user.id, channel.id);
|
||||
await adminClient.addToChannel(recipient1.id, channel.id);
|
||||
await adminClient.addToChannel(recipient2.id, channel.id);
|
||||
|
||||
// Remove admin from channel (they were auto-added as creator)
|
||||
// This ensures exactly 3 members: sender + 2 recipients
|
||||
const adminUser = await adminClient.getMe();
|
||||
await adminClient.removeFromChannel(adminUser.id, channel.id);
|
||||
|
||||
// # Login as sender and navigate to the new channel
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(user);
|
||||
await senderPage.goto(team.name, channel.name);
|
||||
await senderPage.toBeVisible();
|
||||
|
||||
// # Post BoR message
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `Secret message ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
// # Get sender's post
|
||||
let senderPost = await senderPage.getLastPost();
|
||||
|
||||
// * Verify initial state shows 0 recipients revealed
|
||||
await senderPost.burnOnReadBadge.hover();
|
||||
let recipientCount = await senderPost.burnOnReadBadge.getRecipientCount();
|
||||
expect(recipientCount.revealed).toBe(0);
|
||||
expect(recipientCount.total).toBe(2); // Should be 2 recipients (channel has 3 members - 1 sender)
|
||||
|
||||
// # Login as first recipient and reveal the message
|
||||
const {channelsPage: recipient1Page} = await pw.testBrowser.login(recipient1);
|
||||
await recipient1Page.goto(team.name, channel.name);
|
||||
await recipient1Page.toBeVisible();
|
||||
const recipient1Post = await recipient1Page.getLastPost();
|
||||
await recipient1Post.concealedPlaceholder.clickToReveal();
|
||||
await recipient1Post.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// # Refresh sender's view and check updated count
|
||||
await senderPage.page.reload();
|
||||
await senderPage.toBeVisible();
|
||||
senderPost = await senderPage.getLastPost();
|
||||
await expect(senderPost.burnOnReadBadge.container).toBeVisible({timeout: 15000});
|
||||
await senderPost.burnOnReadBadge.hover();
|
||||
recipientCount = await senderPost.burnOnReadBadge.getRecipientCount();
|
||||
expect(recipientCount.revealed).toBe(1);
|
||||
|
||||
// # Login as second recipient and reveal the message
|
||||
const {channelsPage: recipient2Page} = await pw.testBrowser.login(recipient2);
|
||||
await recipient2Page.goto(team.name, channel.name);
|
||||
await recipient2Page.toBeVisible();
|
||||
const recipient2Post = await recipient2Page.getLastPost();
|
||||
await recipient2Post.concealedPlaceholder.clickToReveal();
|
||||
await recipient2Post.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// # Refresh sender's view — all recipients revealed, sender timer should start
|
||||
await senderPage.page.reload();
|
||||
await senderPage.toBeVisible();
|
||||
senderPost = await senderPage.getLastPost();
|
||||
|
||||
// * After all recipients reveal, badge transitions to timer chip
|
||||
await expect(senderPost.burnOnReadTimerChip.container).toBeVisible({timeout: 15000});
|
||||
});
|
||||
|
||||
test('MM-66742_17 sender manually deletes for all recipients', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with BoR enabled
|
||||
const {user, team, adminClient} = await setupBorTest(pw);
|
||||
|
||||
// # Create recipient
|
||||
const recipient = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Login as sender and post BoR message
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(user);
|
||||
await senderPage.goto(team.name, 'town-square');
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `To be deleted ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
// # Get the post and its ID for verification later
|
||||
const senderPost = await senderPage.getLastPost();
|
||||
const postId = await senderPost.getId();
|
||||
|
||||
// # Click flame badge to delete
|
||||
await senderPost.burnOnReadBadge.click();
|
||||
|
||||
// * Verify confirmation modal appears
|
||||
await expect(senderPage.burnOnReadConfirmationModal.container).toBeVisible();
|
||||
|
||||
// # Confirm deletion
|
||||
await senderPage.burnOnReadConfirmationModal.confirm();
|
||||
|
||||
// * Verify the specific post is removed from sender's view
|
||||
// Use attribute selector to handle special characters in post ID (colons, etc.)
|
||||
const deletedPostLocator = senderPage.page.locator(`[id="post_${postId}"]`);
|
||||
await expect(deletedPostLocator).not.toBeVisible();
|
||||
|
||||
// # Login as recipient and verify post is not visible
|
||||
const {channelsPage: recipientPage} = await pw.testBrowser.login(recipient);
|
||||
await recipientPage.goto(team.name, 'town-square');
|
||||
|
||||
// * Verify the deleted message is not in the channel
|
||||
const posts = await recipientPage.centerView.container.locator('.post').all();
|
||||
for (const post of posts) {
|
||||
const text = await post.textContent();
|
||||
expect(text).not.toContain(message);
|
||||
}
|
||||
});
|
||||
|
||||
test('MM-66742_18 sender sees timer after all recipients reveal', {tag: [BOR_TAG]}, async ({pw}) => {
|
||||
// # Initialize setup with short duration
|
||||
const {user, team, adminClient} = await setupBorTest(pw, {
|
||||
durationSeconds: 60,
|
||||
});
|
||||
|
||||
// # Create one recipient
|
||||
const recipient = await createSecondUser(pw, adminClient, team);
|
||||
|
||||
// # Create a private channel with exactly 2 members (sender + recipient)
|
||||
const channelSuffix = Date.now().toString(36);
|
||||
const channel = await adminClient.createChannel(
|
||||
pw.random.channel({
|
||||
teamId: team.id,
|
||||
name: `bor-timer-test-${channelSuffix}`,
|
||||
displayName: `BoR Timer Test ${channelSuffix}`,
|
||||
type: 'P',
|
||||
}),
|
||||
);
|
||||
|
||||
// Add sender and recipient to the channel
|
||||
await adminClient.addToChannel(user.id, channel.id);
|
||||
await adminClient.addToChannel(recipient.id, channel.id);
|
||||
|
||||
// Remove admin from channel (they were auto-added as creator)
|
||||
const adminUser = await adminClient.getMe();
|
||||
await adminClient.removeFromChannel(adminUser.id, channel.id);
|
||||
|
||||
// # Login as sender and post BoR message in the controlled channel
|
||||
const {channelsPage: senderPage} = await pw.testBrowser.login(user);
|
||||
await senderPage.goto(team.name, channel.name);
|
||||
await senderPage.toBeVisible();
|
||||
await senderPage.centerView.postCreate.toggleBurnOnRead();
|
||||
const message = `Timer test ${pw.random.id()}`;
|
||||
await senderPage.postMessage(message);
|
||||
|
||||
// # Get sender's post
|
||||
let senderPost = await senderPage.getLastPost();
|
||||
|
||||
// * Verify sender sees badge with exactly 1 recipient (not timer yet)
|
||||
await expect(senderPost.burnOnReadBadge.container).toBeVisible();
|
||||
await expect(senderPost.burnOnReadTimerChip.container).not.toBeVisible();
|
||||
|
||||
// * Verify recipient count is exactly 1 (our controlled channel)
|
||||
const initialCount = await senderPost.burnOnReadBadge.getRecipientCount();
|
||||
expect(initialCount.total).toBe(1);
|
||||
expect(initialCount.revealed).toBe(0);
|
||||
|
||||
// # Login as recipient and reveal
|
||||
const {channelsPage: recipientPage} = await pw.testBrowser.login(recipient);
|
||||
await recipientPage.goto(team.name, channel.name);
|
||||
await recipientPage.toBeVisible();
|
||||
|
||||
// Get the BoR post (last post in our controlled channel)
|
||||
const recipientPost = await recipientPage.getLastPost();
|
||||
await recipientPost.concealedPlaceholder.clickToReveal();
|
||||
await recipientPost.concealedPlaceholder.waitForReveal();
|
||||
|
||||
// * Verify recipient sees timer chip (their countdown started)
|
||||
await expect(recipientPost.burnOnReadTimerChip.container).toBeVisible();
|
||||
|
||||
// # Refresh sender's view
|
||||
await senderPage.page.reload();
|
||||
await senderPage.toBeVisible();
|
||||
|
||||
// Get the BoR post (last post in our controlled channel)
|
||||
senderPost = await senderPage.getLastPost();
|
||||
|
||||
// * Verify sender now sees timer chip (all 1 recipient revealed, so sender timer starts)
|
||||
await expect(senderPost.burnOnReadTimerChip.container).toBeVisible();
|
||||
await expect(senderPost.burnOnReadBadge.container).not.toBeVisible();
|
||||
|
||||
// * Verify timer is counting down
|
||||
const timeRemaining = await senderPost.burnOnReadTimerChip.getTimeRemaining();
|
||||
expect(timeRemaining).toMatch(/\d+:\d+/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import type {PlaywrightClient4, PlaywrightExtended} from '@mattermost/playwright-lib';
|
||||
|
||||
export const BOR_TAG = '@burn_on_read';
|
||||
|
||||
export type BorSetup = {
|
||||
adminClient: PlaywrightClient4;
|
||||
adminUser: UserProfile;
|
||||
user: UserProfile;
|
||||
userClient: PlaywrightClient4;
|
||||
team: Team;
|
||||
offTopicUrl: string;
|
||||
townSquareUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup test environment with BoR feature enabled
|
||||
* @param pw Playwright extended fixture
|
||||
* @param options Configuration options
|
||||
* @returns Setup data including users, team, and clients
|
||||
*/
|
||||
export async function setupBorTest(
|
||||
pw: PlaywrightExtended,
|
||||
options: {
|
||||
durationSeconds?: number;
|
||||
maxTTLSeconds?: number;
|
||||
} = {},
|
||||
): Promise<BorSetup> {
|
||||
const {durationSeconds = 60, maxTTLSeconds = 300} = options;
|
||||
|
||||
// Ensure prerequisites
|
||||
await pw.ensureLicense();
|
||||
await pw.skipIfNoLicense();
|
||||
|
||||
// Initialize setup
|
||||
const setup = await pw.initSetup();
|
||||
|
||||
// Enable BoR via API - patch config instead of full update
|
||||
const currentConfig = await setup.adminClient.getConfig();
|
||||
await setup.adminClient.patchConfig({
|
||||
ServiceSettings: {
|
||||
...currentConfig.ServiceSettings,
|
||||
EnableBurnOnRead: true,
|
||||
BurnOnReadDurationSeconds: durationSeconds,
|
||||
BurnOnReadMaximumTimeToLiveSeconds: maxTTLSeconds,
|
||||
},
|
||||
});
|
||||
|
||||
return setup as BorSetup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a second user and add to team
|
||||
* @param pw Playwright extended fixture
|
||||
* @param adminClient Admin client for user creation
|
||||
* @param team Team to add user to
|
||||
* @returns Created user with password
|
||||
*/
|
||||
export async function createSecondUser(
|
||||
pw: PlaywrightExtended,
|
||||
adminClient: PlaywrightClient4,
|
||||
team: Team,
|
||||
): Promise<UserProfile & {password: string}> {
|
||||
const [user] = await adminClient.createUsers(team.id, 1);
|
||||
return user as UserProfile & {password: string};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse recipient count from tooltip text
|
||||
* @param tooltipText Tooltip text containing recipient info
|
||||
* @returns Object with revealed and total counts
|
||||
*/
|
||||
export function parseRecipientCount(tooltipText: string): {revealed: number; total: number} {
|
||||
const match = tooltipText.match(/Read by (\d+) of (\d+)/);
|
||||
if (!match) {
|
||||
throw new Error(`Could not parse recipient count from: ${tooltipText}`);
|
||||
}
|
||||
return {
|
||||
revealed: parseInt(match[1], 10),
|
||||
total: parseInt(match[2], 10),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse time remaining from timer text
|
||||
* @param timerText Timer text (e.g., "0:45", "1:30")
|
||||
* @returns Seconds remaining
|
||||
*/
|
||||
export function parseTimeRemaining(timerText: string): number {
|
||||
const parts = timerText.split(':');
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(`Invalid timer format: ${timerText}`);
|
||||
}
|
||||
const minutes = parseInt(parts[0], 10);
|
||||
const seconds = parseInt(parts[1], 10);
|
||||
return minutes * 60 + seconds;
|
||||
}
|
||||
Reference in New Issue
Block a user