Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ Legends:
- [`webpack@5.107.2`](https://npmjs.com/package/webpack/v/5.107.2)
- [`yaml@2.9.0`](https://npmjs.com/package/yaml/v/2.9.0)

### Fixed

- Fixed the "Retry" button should resend the message after failed to send, instead of showing "Render error", by [@compulim](https://github.com/compulim) in PR [#5838](https://github.com/microsoft/BotFramework-WebChat/pull/5838)
Comment thread
compulim marked this conversation as resolved.
Outdated

## [4.19.0] - 2026-05-25

Breaking changes in this release:
Expand Down
135 changes: 135 additions & 0 deletions __tests__/html2/activityStatus/sendFailedRetry.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<main id="webchat"></main>
<script>
function activityStatusInnerTextContained(expectedInnerTextPattern, index = 0) {
return pageConditions.became(
`Activity status should contains "${expectedInnerTextPattern}"`,
() => pageElements.activityStatuses()[index].innerText.includes(expectedInnerTextPattern),
1000
);
}

function activityChannelDataStateBecame(expectedState, index = 0) {
if (typeof expectedState === 'undefined') {
return pageConditions.became(
`Activity "channelData.state" should be undefined`,
() => !('state' in pageObjects.getActivities()[index].channelData),
1000
);
}

return pageConditions.became(
`Activity "channelData.state" should be "${expectedState}"`,
() => pageObjects.getActivities()[index].channelData?.state === expectedState,
1000
);
}

function activityChannelDataWebChatSendStatusBecame(expectedSendStatus, index = 0) {
return pageConditions.became(
`Activity "channelData['webchat:send-status']" should be "${expectedSendStatus}"`,
() => pageObjects.getActivities()[index].channelData?.['webchat:send-status'] === expectedSendStatus,
1000
);
}

run(
async function () {
const clock = lolex.createClock();

const { directLine, store } = testHelpers.createDirectLineEmulator({ ponyfill: clock });

WebChat.renderWebChat(
{
directLine,
ponyfill: clock,
store,
styleOptions: { spinnerAnimationBackgroundImage: 'url(/assets/staticspinner.png)' }
},
document.getElementById('webchat')
);

await pageConditions.webChatRendered();

clock.tick(400);

// SETUP: Send a message and resolve the call to `postActivity()`.
await pageConditions.uiConnected();

const sendMessage = await directLine.actPostActivity(
() => pageObjects.sendMessageViaSendBox('Hello, World!', { waitForSend: false }),
{ id: 'a00001' }
);

// THEN: `channelData.state` should be "sending".
await activityChannelDataStateBecame('sending');

// THEN: `channelData['webchat:send-status']` should be "sending".
await activityChannelDataWebChatSendStatusBecame('sending');

// THEN: The message should have status of "Sending".
await activityStatusInnerTextContained('Sending');

// THEN: It should match snapshot.
await host.snapshot('local');

Comment thread
compulim marked this conversation as resolved.
// WHEN: After 20 seconds.
clock.tick(20000);

// THEN: `channelData.state` should be "send failed".
await activityChannelDataStateBecame('send failed');

// THEN: `channelData['webchat:send-status']` should be "sending".
await activityChannelDataWebChatSendStatusBecame('sending');

// THEN: The message should have status of "Send failed. Retry."
// This is because the message passed 20s as defined in `styleOptions.sendTimeout`.
await activityStatusInnerTextContained('Send failed. Retry.');

// THEN: It should match snapshot.
await host.snapshot('local');

// WHEN: The retry button is clicked.
const sendMessageRetry = await directLine.actPostActivity(() =>
host.click(pageElements.activityStatuses()[0].querySelector('button'))
);

// THEN: It should show 2 outgoing messages.
await pageConditions.numActivitiesShown(2);

// THEN: It should match snapshot.
await host.snapshot('local');

// WHEN: The first outgoing message is sent.
await sendMessage.resolveAll();

// THEN: The activity status of the first message should be "Just now."
await activityStatusInnerTextContained('Just now', 0);
await activityStatusInnerTextContained('Sending', 1);

// THEN: It should match snapshot.
await host.snapshot('local');

// WHEN: The second outgoing message is sent.
await sendMessageRetry.resolveAll();

// THEN: The activity status of the second message should be "Just now."
await activityStatusInnerTextContained('Just now', 0);
await activityStatusInnerTextContained('Just now', 1);

// THEN: It should match snapshot.
await host.snapshot('local');
},
{ ignoreErrors: true }
);
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion packages/core/src/sagas/postActivitySaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function* postActivity(
channelData: {
// `channelData.state` is being deprecated in favor of `channelData['webchat:send-status']`.
// Please refer to #4362 for details. Remove on or after 2024-07-31.
...deleteKey(activity.channelData, 'state'),
...deleteKey(activity.channelData, 'state', 'webchat:internal:local-id'),
clientActivityID
Comment thread
compulim marked this conversation as resolved.
},
channelId: 'webchat',
Expand Down
Loading