fix(ci): eliminate remaining networkidle2 hangs and fix research submit test

- auth_helper.js: replace 4 networkidle2 references in logout() with
  domcontentloaded to prevent hang when WebSocket/polling keeps network active
- test_research_submit.js: fix race condition where setupDefaultModel()
  triggers provider-change JS that asynchronously clears the query field;
  add 1.5s settle delay and verify query text persists before submitting
- test_research_form_ci.js: remove unused formSubmitWithQuery and
  enterKeySubmits methods (dead code never called in main())
This commit is contained in:
LearningCircuit
2026-03-12 00:13:16 +01:00
parent 448c09c2f3
commit a51bba5e1c
3 changed files with 19 additions and 106 deletions
+4 -4
View File
@@ -720,7 +720,7 @@ class AuthHelper {
if (logoutForm) {
await Promise.all([
this.page.waitForNavigation({
waitUntil: 'networkidle2',
waitUntil: 'domcontentloaded',
timeout: AUTH_CONFIG.timeouts.logout
}).catch(() => {}),
this.page.evaluate(() => {
@@ -732,7 +732,7 @@ class AuthHelper {
if (logoutLink) {
await Promise.all([
this.page.waitForNavigation({
waitUntil: 'networkidle2',
waitUntil: 'domcontentloaded',
timeout: AUTH_CONFIG.timeouts.logout
}).catch(() => {}),
this.page.click('a.logout-btn')
@@ -740,7 +740,7 @@ class AuthHelper {
} else {
// Last resort: navigate directly to logout URL
await this.page.goto(`${this.page.url().split('/').slice(0, 3).join('/')}${AUTH_CONFIG.paths.logout}`, {
waitUntil: 'networkidle2',
waitUntil: 'domcontentloaded',
timeout: AUTH_CONFIG.timeouts.logout
});
}
@@ -756,7 +756,7 @@ class AuthHelper {
} else {
// Double-check by trying to access a protected page
await this.page.goto(`${this.page.url().split('/').slice(0, 3).join('/')}/settings/`, {
waitUntil: 'networkidle2',
waitUntil: 'domcontentloaded',
timeout: AUTH_CONFIG.timeouts.formSelector
}).catch(() => {});
-93
View File
@@ -573,54 +573,6 @@ const AdvancedOptionsTests = {
// Form Submission Tests
// ============================================================================
const FormSubmissionTests = {
async formSubmitWithQuery(page, baseUrl) {
await navigateTo(page, `${baseUrl}/`);
// Fill in query
await page.evaluate(() => {
const queryInput = document.querySelector('textarea[name="query"], input[name="query"], #query');
if (queryInput) {
queryInput.value = 'What is the capital of France?';
queryInput.dispatchEvent(new Event('input', { bubbles: true }));
}
});
// Submit form
const [response] = await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => null),
page.click('button[type="submit"], .start-research, #start-research').catch(() => {})
]);
await delay(1000);
const result = await page.evaluate(() => {
const url = window.location.href;
const hasProgress = url.includes('progress') || url.includes('research');
const hasResearchId = /\/research\/|\/progress\/|research_id=/.test(url);
// Check for progress indicators
const progressBar = document.querySelector('.progress, .progress-bar, [role="progressbar"]');
const statusText = document.querySelector('.status, .research-status, [class*="status"]');
return {
url,
hasProgress,
hasResearchId,
hasProgressBar: !!progressBar,
hasStatusText: !!statusText
};
});
const submitted = result.hasProgress || result.hasResearchId || result.hasProgressBar;
return {
passed: submitted,
message: submitted
? `Form submitted successfully (url contains progress/research: ${result.hasProgress}, has progress bar: ${result.hasProgressBar})`
: `Form submission unclear (stayed at: ${result.url})`
};
},
async formValidationEmptyQuery(page, baseUrl) {
await navigateTo(page, `${baseUrl}/`);
await delay(500);
@@ -682,51 +634,6 @@ const FormSubmissionTests = {
// Keyboard Interaction Tests
// ============================================================================
const KeyboardTests = {
async enterKeySubmits(page, baseUrl) {
await navigateTo(page, `${baseUrl}/`);
// Focus query input and type
const queryInput = await page.$('textarea[name="query"], input[name="query"], #query');
if (!queryInput) {
return { passed: null, skipped: true, message: 'Query input not found' };
}
await queryInput.type('Test query for keyboard submission');
// Check if textarea or input (behavior differs)
const isTextarea = await page.evaluate(() => {
const input = document.querySelector('textarea[name="query"], input[name="query"], #query');
return input?.tagName?.toLowerCase() === 'textarea';
});
if (isTextarea) {
// For textarea, Ctrl+Enter should submit
await page.keyboard.down('Control');
await page.keyboard.press('Enter');
await page.keyboard.up('Control');
} else {
// For input, Enter should submit
await page.keyboard.press('Enter');
}
await delay(1000);
const result = await page.evaluate(() => {
const url = window.location.href;
return {
url,
navigated: url.includes('progress') || url.includes('research')
};
});
return {
passed: result.navigated,
message: result.navigated
? `${isTextarea ? 'Ctrl+Enter' : 'Enter'} key submits form`
: `Keyboard submit did not navigate (url: ${result.url})`
};
},
async shiftEnterNewline(page, baseUrl) {
// Shift+Enter newline behavior is unreliable in headless Chrome
if (process.env.CI) {
+15 -9
View File
@@ -20,14 +20,6 @@ const path = require('path');
let browser;
const isCI = !!process.env.CI;
// Skip in CI: setupDefaultModel() triggers provider-change JS that clears the
// query field, causing the form to submit empty and redirect to /. This is a
// test-infrastructure issue, not a product bug.
if (isCI) {
console.log('⏭️ Skipping research submit test in CI (known model-setup race condition)');
process.exit(0);
}
console.log(`🧪 Running research submit test (CI mode: ${isCI})`);
// Create screenshots directory
const screenshotsDir = path.join(__dirname, 'screenshots');
@@ -62,16 +54,30 @@ const path = require('path');
await page.goto('http://127.0.0.1:5000/', { waitUntil: 'domcontentloaded' });
// Set up model configuration
// NOTE: setupDefaultModel() calls page.select('#model_provider') which
// triggers frontend JS that asynchronously resets the form (including
// clearing the query field). We must wait for that to settle before typing.
console.log('🔧 Configuring model...');
const modelConfigured = await setupDefaultModel(page);
if (!modelConfigured) {
throw new Error('Failed to configure model');
}
// Wait for provider-change JS to finish resetting the form
await new Promise(resolve => setTimeout(resolve, 1500));
// Wait for and fill the query field
await page.waitForSelector('#query', { timeout: 10000 });
// Clear any residual value then type
await page.$eval('#query', el => { el.value = ''; });
await page.type('#query', 'What is Node.js?');
console.log('✅ Query entered');
// Verify the query actually stuck (guard against async clear)
const queryValue = await page.$eval('#query', el => el.value);
if (!queryValue || queryValue.trim() === '') {
throw new Error('Query field was cleared after typing — provider-change race condition');
}
console.log('✅ Query entered:', queryValue);
// Check if model and search engine are pre-selected
const formValues = await page.evaluate(() => {