/**
* Hypothesis Challenge Flow E2E Tests
*
* Tests the complete flow for hypothesis challenge requests including:
* - Hypothesis display with agreement scale
* - Emoji-based agreement level selection
* - Individual hypothesis comments
* - "Won't answer" options for hypotheses
* - Challenge-level "won't answer" with explanation
* - Completion status buttons
* - Tool redirect functionality
*/
import { test, expect } from '@playwright/test';
import { RequestHandlerPage } from '../support/page-objects/request-handler.page';
import { HypothesisResponsePage } from '../support/page-objects/response-components.page';
import { ServerManager, TEST_PORTS, getAvailablePort } from '../support/test-utils/server-manager';
import { createHypothesisChallengeRequest } from '../support/test-utils/test-data';
import {
mockBrowserNotifications,
waitForSSEConnection,
waitForNetworkRequest,
logTestStep,
randomString
} from '../support/test-utils/helpers';
test.describe('Hypothesis Challenge Flow', () => {
let serverManager: ServerManager;
let requestHandler: RequestHandlerPage;
let hypothesisResponse: HypothesisResponsePage;
test.beforeEach(async ({ page }) => {
await mockBrowserNotifications(page);
const availablePort = await getAvailablePort(TEST_PORTS.HYPOTHESIS_CHALLENGE);
serverManager = new ServerManager({
port: availablePort,
debug: true
});
logTestStep('Starting MCP server', { port: availablePort });
await serverManager.start();
requestHandler = new RequestHandlerPage(page);
hypothesisResponse = new HypothesisResponsePage(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 hypothesis challenge correctly', async ({ page }) => {
logTestStep('Testing hypothesis challenge display');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
// Wait for request to appear in UI
await expect(requestHandler.currentRequest).toBeVisible();
// Verify request type is detected correctly
const requestType = await requestHandler.getCurrentRequestType();
expect(requestType).toBe('hypothesis-challenge');
// Verify hypothesis component is visible
await expect(hypothesisResponse.component).toBeVisible();
// Check hypothesis count
const hypothesisCount = await hypothesisResponse.getHypothesisCount();
expect(hypothesisCount).toBeGreaterThan(0);
// Verify submit button is present
await expect(hypothesisResponse.submitButton).toBeVisible();
logTestStep('Hypothesis challenge displayed correctly', { hypothesisCount });
});
test('should handle agreement level selection', async ({ page }) => {
logTestStep('Testing agreement level selection');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Set agreement levels for different hypotheses
// Level 0 = Strongly Disagree, Level 6 = Strongly Agree (7-point scale)
await hypothesisResponse.setAgreementLevel(0, 5); // Mostly agree
await hypothesisResponse.setAgreementLevel(1, 2); // Somewhat disagree
await hypothesisResponse.setAgreementLevel(2, 6); // Strongly agree
// Verify selections are reflected in UI
const firstHypothesis = hypothesisResponse.hypotheses.nth(0);
const fifthEmoji = firstHypothesis.locator('.agreement-scale .agreement-button').nth(5);
await expect(fifthEmoji).toHaveClass(/selected|active/);
logTestStep('Agreement level selection verified');
});
test('should handle hypothesis comments', async ({ page }) => {
logTestStep('Testing hypothesis comments');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Add comments to hypotheses
const comment1 = 'Microservices can improve scalability but add operational complexity';
const comment2 = 'Event-driven architecture reduces coupling but requires careful design';
await hypothesisResponse.addHypothesisComment(0, comment1);
await hypothesisResponse.addHypothesisComment(1, comment2);
// Verify comments were added
const firstHypothesis = hypothesisResponse.hypotheses.nth(0);
const firstCommentField = firstHypothesis.locator('.comment-section .comment-input');
await expect(firstCommentField).toHaveValue(comment1);
const secondHypothesis = hypothesisResponse.hypotheses.nth(1);
const secondCommentField = secondHypothesis.locator('.comment-section .comment-input');
await expect(secondCommentField).toHaveValue(comment2);
logTestStep('Hypothesis comments verified');
});
test('should handle individual hypothesis "won\'t answer"', async ({ page }) => {
logTestStep('Testing individual hypothesis won\'t answer');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Set "won't answer" for the second hypothesis
await hypothesisResponse.setWontAnswerHypothesis(1);
// Verify the checkbox is checked
const secondHypothesis = hypothesisResponse.hypotheses.nth(1);
const wontAnswerCheckbox = secondHypothesis.locator('input[type="checkbox"]');
await expect(wontAnswerCheckbox).toBeChecked();
// Agreement scale should be hidden when "won't answer" is selected
const emojiScale = secondHypothesis.locator('.agreement-scale');
await expect(emojiScale).toBeHidden();
logTestStep('Individual hypothesis won\'t answer verified');
});
test('should handle challenge-level "won\'t answer"', async ({ page }) => {
logTestStep('Testing challenge-level won\'t answer');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Set "won't answer" for entire challenge
const explanation = 'I don\'t have enough context to evaluate these architectural hypotheses properly';
await hypothesisResponse.setWontAnswerChallenge(explanation);
// Verify the checkbox is checked and explanation is filled
await expect(hypothesisResponse.wontAnswerChallenge.locator('input[type="checkbox"]')).toBeChecked();
const whySection = page.locator('app-hypothesis-response .why-section');
await expect(whySection.locator('.why-input')).toHaveValue(explanation);
logTestStep('Challenge-level won\'t answer verified');
});
test('should submit hypothesis challenge response', async ({ page }) => {
logTestStep('Testing hypothesis challenge response submission');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Set agreement levels and comments
await hypothesisResponse.setAgreementLevel(0, 4); // Agree
await hypothesisResponse.setAgreementLevel(1, 5); // Mostly agree
await hypothesisResponse.setAgreementLevel(2, 1); // Disagree
await hypothesisResponse.addHypothesisComment(0, 'Microservices help with team autonomy');
await hypothesisResponse.addHypothesisComment(1, 'Events reduce direct dependencies');
await hypothesisResponse.addHypothesisComment(2, 'K8s complexity is manageable with proper tooling');
// Submit response
const responsePromise = waitForNetworkRequest(page, '/mcp/response');
await hypothesisResponse.submit();
await responsePromise;
// Request should be cleared after submission
await expect(requestHandler.currentRequest).toBeHidden();
await expect(requestHandler.waitingMessage).toBeVisible();
logTestStep('Hypothesis challenge response submitted successfully');
});
test('should handle completion status buttons', async ({ page }) => {
logTestStep('Testing completion status buttons');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Set some agreement levels
await hypothesisResponse.setAgreementLevel(0, 3);
await hypothesisResponse.setAgreementLevel(1, 5);
// Test "Done" button
const doneResponsePromise = waitForNetworkRequest(page, '/mcp/response');
await hypothesisResponse.submitWithDone();
await doneResponsePromise;
logTestStep('Done button functionality verified');
// Test "Drill Deeper" button with new request
const drillRequest = createHypothesisChallengeRequest({
question: 'Follow-up hypothesis challenge'
});
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: drillRequest
});
await expect(hypothesisResponse.component).toBeVisible();
await hypothesisResponse.setAgreementLevel(0, 2);
const drillResponsePromise = waitForNetworkRequest(page, '/mcp/response');
await hypothesisResponse.submitWithDrillDeeper();
await drillResponsePromise;
logTestStep('Drill deeper button functionality verified');
});
test('should handle tool redirect functionality', async ({ page }) => {
logTestStep('Testing tool redirect functionality');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Test redirect to open question
const openQuestionRedirectPromise = waitForNetworkRequest(page, '/mcp/response');
await hypothesisResponse.clickOpenQuestionBailout();
await openQuestionRedirectPromise;
// Should clear current request and show waiting state
await expect(requestHandler.currentRequest).toBeHidden();
logTestStep('Open question redirect verified');
// Test redirect to multiple choice
const multipleChoiceRequest = createHypothesisChallengeRequest({
question: 'Another hypothesis for multiple choice testing'
});
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: multipleChoiceRequest
});
await expect(hypothesisResponse.component).toBeVisible();
const multipleChoiceRedirectPromise = waitForNetworkRequest(page, '/mcp/response');
await hypothesisResponse.clickMultipleChoiceBailout();
await multipleChoiceRedirectPromise;
logTestStep('Multiple choice redirect verified');
});
test('should handle emoji scale interaction', async ({ page }) => {
logTestStep('Testing emoji scale interaction');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
const firstHypothesis = hypothesisResponse.hypotheses.nth(0);
const emojiScale = firstHypothesis.locator('.agreement-scale');
// Should have 7 emoji buttons (0-6 scale)
const emojiButtons = emojiScale.locator('button');
const buttonCount = await emojiButtons.count();
expect(buttonCount).toBe(7);
// Test clicking different agreement levels
for (let i = 0; i < 3; i++) {
await emojiButtons.nth(i).click();
await expect(emojiButtons.nth(i)).toHaveClass(/selected|active/);
}
logTestStep('Emoji scale interaction verified', { buttonCount });
});
test('should handle complex hypothesis scenarios', async ({ page }) => {
logTestStep('Testing complex hypothesis scenarios');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Mixed scenario: some answered, some won't answer, some with comments
await hypothesisResponse.setAgreementLevel(0, 4); // Agree
await hypothesisResponse.addHypothesisComment(0, 'Detailed reasoning here');
await hypothesisResponse.setWontAnswerHypothesis(1); // Won't answer second
await hypothesisResponse.setAgreementLevel(2, 1); // Disagree with third
await hypothesisResponse.addHypothesisComment(2, 'Strong disagreement because...');
// Should still be able to submit
await expect(hypothesisResponse.submitButton).toBeEnabled();
const responsePromise = waitForNetworkRequest(page, '/mcp/response');
await hypothesisResponse.submit();
await responsePromise;
logTestStep('Complex hypothesis scenario verified');
});
test('should handle long hypothesis statements', async ({ page }) => {
logTestStep('Testing long hypothesis statements');
// Create request with very long hypothesis statements
const longHypothesisRequest = createHypothesisChallengeRequest();
// Extend the first hypothesis with a long statement
if (longHypothesisRequest.context &&
typeof longHypothesisRequest.context === 'object' &&
'challenge' in longHypothesisRequest.context) {
const challenge = longHypothesisRequest.context.challenge as any;
challenge.hypotheses[0].statement = 'This is a very long hypothesis statement that tests how the UI handles extensive text content in hypothesis evaluations. '.repeat(5);
}
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: longHypothesisRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Long hypothesis should be displayed without breaking layout
const firstHypothesis = hypothesisResponse.hypotheses.nth(0);
await expect(firstHypothesis).toBeVisible();
// Emoji scale should still be functional
await hypothesisResponse.setAgreementLevel(0, 3);
const emojiButton = firstHypothesis.locator('.agreement-scale .agreement-button').nth(3);
await expect(emojiButton).toHaveClass(/selected|active/);
logTestStep('Long hypothesis statements handled correctly');
});
test('should handle responsive design for hypotheses', async ({ page }) => {
logTestStep('Testing responsive design for hypothesis challenge');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Test mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
await expect(hypothesisResponse.component).toBeVisible();
await expect(hypothesisResponse.hypotheses.first()).toBeVisible();
// Emoji scale should still be usable on mobile
await hypothesisResponse.setAgreementLevel(0, 2);
// Test desktop viewport
await page.setViewportSize({ width: 1200, height: 800 });
await expect(hypothesisResponse.component).toBeVisible();
await expect(hypothesisResponse.hypotheses.first()).toBeVisible();
logTestStep('Responsive design verified');
});
test('should validate form state combinations', async ({ page }) => {
logTestStep('Testing form state validation');
const testRequest = createHypothesisChallengeRequest();
await page.request.post(`${serverManager.getServerUrl()}/mcp/simulate-request`, {
data: testRequest
});
await expect(hypothesisResponse.component).toBeVisible();
// Test submitting with no selections (should be allowed)
await expect(hypothesisResponse.submitButton).toBeEnabled();
// Test partial completion
await hypothesisResponse.setAgreementLevel(0, 3);
await expect(hypothesisResponse.submitButton).toBeEnabled();
// Test with challenge-level won't answer
await hypothesisResponse.setWontAnswerChallenge('Cannot evaluate these hypotheses');
await expect(hypothesisResponse.submitButton).toBeEnabled();
logTestStep('Form state validation verified');
});
});