/**
* Single Question Flow E2E Tests
*
* Tests the complete flow for single question requests including:
* - Question display and rendering
* - Response input and submission
* - Completion status buttons
* - Tool redirect functionality
* - Timeout and cancellation handling
*/
import { test, expect } from '@playwright/test';
import { RequestHandlerPage } from '../support/page-objects/request-handler.page';
import { ResponseInputPage } from '../support/page-objects/response-components.page';
import { ServerManager, TEST_PORTS, getAvailablePort } from '../support/test-utils/server-manager';
import { createSingleQuestionRequest, createMarkdownQuestionRequest, TEST_SCENARIOS } from '../support/test-utils/test-data';
import {
mockBrowserNotifications,
waitForSSEConnection,
waitForNetworkRequest,
logTestStep,
fillAndVerify,
randomString
} from '../support/test-utils/helpers';
test.describe('Single Question Flow', () => {
let serverManager: ServerManager;
let requestHandler: RequestHandlerPage;
let responseInput: ResponseInputPage;
test.beforeEach(async ({ page }) => {
await mockBrowserNotifications(page);
const availablePort = await getAvailablePort(TEST_PORTS.SINGLE_QUESTION);
serverManager = new ServerManager({
port: availablePort,
debug: true
});
logTestStep('Starting MCP server', { port: availablePort });
await serverManager.start();
requestHandler = new RequestHandlerPage(page);
responseInput = new ResponseInputPage(page);
await requestHandler.goto(availablePort);
await requestHandler.waitForLoad();
await waitForSSEConnection(page);
await requestHandler.waitForConnection();
});
test.afterEach(async () => {
if (serverManager) {
await serverManager.stop();
}
});
test('should display single question request correctly', async ({ page }) => {
logTestStep('Testing single question display');
const testRequest = createSingleQuestionRequest({
question: 'What is your favorite programming language and why?'
});
// Simulate receiving a request via HTTP POST to trigger SSE
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
// Wait for request to appear in UI
await expect(requestHandler.currentRequest).toBeVisible();
// Verify question is displayed
const question = await requestHandler.getCurrentRequestQuestion();
expect(question).toContain('programming language');
// Verify response input is visible
await expect(responseInput.component).toBeVisible();
await expect(responseInput.textarea).toBeVisible();
await expect(responseInput.submitButton).toBeVisible();
logTestStep('Single question displayed correctly');
});
test('should handle text response submission', async ({ page }) => {
logTestStep('Testing text response submission');
const testRequest = createSingleQuestionRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Fill response text
const responseText = 'Python is my favorite because of its simplicity and powerful libraries.';
await fillAndVerify(responseInput.textarea, responseText);
// Submit response
const responsePromise = waitForNetworkRequest(page, '/mcp/response');
await responseInput.submit();
await responsePromise;
// Request should be cleared after submission
await expect(requestHandler.currentRequest).toBeHidden();
await expect(requestHandler.waitingMessage).toBeVisible();
logTestStep('Text response submitted successfully');
});
test('should handle completion status buttons', async ({ page }) => {
logTestStep('Testing completion status buttons');
const testRequest = createSingleQuestionRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Test "Done" button
await fillAndVerify(responseInput.textarea, 'Final answer');
const doneResponsePromise = waitForNetworkRequest(page, '/mcp/response');
await responseInput.submitWithDone();
await doneResponsePromise;
logTestStep('Done button functionality verified');
// Test "Drill Deeper" button with new request
const drillRequest = createSingleQuestionRequest({
question: 'Can you elaborate on your previous answer?'
});
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: drillRequest
});
await expect(requestHandler.currentRequest).toBeVisible({ timeout: 20000 });
await fillAndVerify(responseInput.textarea, 'More detailed explanation');
const drillResponsePromise = waitForNetworkRequest(page, '/mcp/response');
await responseInput.submitWithDrillDeeper();
await drillResponsePromise;
logTestStep('Drill deeper button functionality verified');
});
test('should handle tool redirect functionality', async ({ page }) => {
logTestStep('Testing tool redirect functionality');
const testRequest = createSingleQuestionRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Test redirect to multiple choice
const multipleChoiceRedirectPromise = waitForNetworkRequest(page, '/mcp/response');
await responseInput.clickMultipleChoiceBailout();
await multipleChoiceRedirectPromise;
// Should clear current request and show waiting state
await expect(requestHandler.currentRequest).toBeHidden();
logTestStep('Multiple choice redirect verified');
// Test redirect to hypothesis challenge
const hypothesisRequest = createSingleQuestionRequest({
question: 'Another question for hypothesis testing'
});
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: hypothesisRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
const hypothesisRedirectPromise = waitForNetworkRequest(page, '/mcp/response');
await responseInput.clickHypothesisBailout();
await hypothesisRedirectPromise;
logTestStep('Hypothesis challenge redirect verified');
});
test('should handle markdown rendering in questions', async ({ page }) => {
logTestStep('Testing markdown rendering');
const markdownRequest = createMarkdownQuestionRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: markdownRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Check if markdown elements are rendered
const requestDisplay = requestHandler.requestDisplay;
// Look for markdown-rendered elements
const heading = requestDisplay.locator('h1, h2, h3');
const bold = requestDisplay.locator('strong, b');
const italic = requestDisplay.locator('em, i');
const code = requestDisplay.locator('code');
const blockquote = requestDisplay.locator('blockquote');
// At least some markdown elements should be present
const hasMarkdownElements = await Promise.all([
heading.count(),
bold.count(),
italic.count(),
code.count(),
blockquote.count()
]).then(counts => counts.some(count => count > 0));
expect(hasMarkdownElements).toBe(true);
logTestStep('Markdown rendering verified');
});
test('should handle character limit validation', async ({ page }) => {
logTestStep('Testing character limit validation');
const testRequest = createSingleQuestionRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Fill with long text (over 1000 characters)
const longText = randomString(1200);
await responseInput.fillResponse(longText);
// Character count should be displayed
const charCount = await responseInput.getCharacterCount();
expect(charCount).toContain('1200');
// Submit button should be disabled when over character limit
await expect(responseInput.submitButton).toBeDisabled();
logTestStep('Character limit validation verified');
});
test('should handle keyboard shortcuts', async ({ page }) => {
logTestStep('Testing keyboard shortcuts');
const testRequest = createSingleQuestionRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Fill response and use Ctrl+Enter to submit
await fillAndVerify(responseInput.textarea, 'Keyboard shortcut test');
const keyboardSubmitPromise = waitForNetworkRequest(page, '/mcp/response');
await responseInput.submitWithKeyboard();
await keyboardSubmitPromise;
await expect(requestHandler.currentRequest).toBeHidden();
logTestStep('Keyboard shortcuts verified');
});
test('should handle form clearing', async ({ page }) => {
logTestStep('Testing form clearing');
const testRequest = createSingleQuestionRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Fill some text
await fillAndVerify(responseInput.textarea, 'Some response text');
// Clear the form
await responseInput.clear();
// Textarea should be empty
await expect(responseInput.textarea).toHaveValue('');
logTestStep('Form clearing verified');
});
test('should handle long questions gracefully', async ({ page }) => {
logTestStep('Testing long question handling');
const longRequest = TEST_SCENARIOS.LONG_QUESTION;
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: longRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Long question should be displayed without breaking layout
const questionText = await requestHandler.getCurrentRequestQuestion();
expect(questionText.length).toBeGreaterThan(500);
// Response input should still be functional
await expect(responseInput.component).toBeVisible();
await expect(responseInput.textarea).toBeVisible();
logTestStep('Long question handling verified');
});
test('should handle request timeout scenarios', async ({ page }) => {
logTestStep('Testing request timeout handling');
const testRequest = createSingleQuestionRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Simulate timeout message from server
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-timeout`, {
data: { requestId: testRequest.id }
});
// Should show timeout message
await requestHandler.waitForTimeoutMessage();
await expect(requestHandler.timeoutMessage).toContainText('Request Timed Out');
// Current request should be cleared
await expect(requestHandler.currentRequest).toBeHidden();
logTestStep('Request timeout handling verified');
});
test('should handle request cancellation scenarios', async ({ page }) => {
logTestStep('Testing request cancellation handling');
const testRequest = createSingleQuestionRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(requestHandler.currentRequest).toBeVisible();
// Simulate cancellation message from server
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-cancellation`, {
data: { requestId: testRequest.id }
});
// Should show cancellation message
await requestHandler.waitForCancellationMessage();
await expect(requestHandler.cancellationMessage).toContainText('Aborted by Client');
// Current request should be cleared
await expect(requestHandler.currentRequest).toBeHidden();
logTestStep('Request cancellation handling verified');
});
});