Skip to content
Open
Changes from all 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
207 changes: 177 additions & 30 deletions tests/specs/code-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ async function setupCodePage(page: any) {
await page.goto('/code')

// Set localStorage to mark onboarding as completed
// Also mark code feature onboarding as completed to skip the "Bug or feature?" page
await page.evaluate(() => {
localStorage.setItem('user_onboarding_completed', 'true')
localStorage.setItem('onboarding_in_progress', '')
localStorage.removeItem('onboarding_in_progress')
localStorage.setItem('code_onboarding_completed', 'true')
localStorage.setItem('code_feature_selected', 'cloud')
})

// Reload page - now onboarding should be skipped
Expand Down Expand Up @@ -47,63 +50,187 @@ async function setupCodePage(page: any) {
// Wait for page to stabilize
await page.waitForTimeout(1000)

// Handle "Bug or feature?" onboarding page if present
// This page appears for first-time code feature users
// Wait a bit longer for the page to fully render
await page.waitForTimeout(2000)

// Check for the onboarding page by looking for the characteristic heading
const onboardingHeading = page.locator('h2', { hasText: '选择最适合您的方式继续编码之旅' }).first()
const isOnboardingVisible = await onboardingHeading.isVisible({ timeout: 5000 }).catch(() => false)
Comment on lines +58 to +60
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use the onboarding test IDs here instead of localized copy/XPath.

This path still depends on Chinese headings/text and DOM ancestry, so it will miss the dialog in the English UI or after a copy/layout tweak. The onboarding cards already have stable test IDs in the PR context; use those directly and drop the XPath/text fallbacks.

🎯 Suggested change
-  const onboardingHeading = page.locator('h2', { hasText: '选择最适合您的方式继续编码之旅' }).first()
-  const isOnboardingVisible = await onboardingHeading.isVisible({ timeout: 5000 }).catch(() => false)
+  const cloudIdeOption = page.getByTestId('onboarding-cloud-ide-option').first()
+  const ideOption = page.getByTestId('onboarding-ide-option').first()
+  const isOnboardingVisible =
+    (await cloudIdeOption.isVisible({ timeout: 5000 }).catch(() => false)) ||
+    (await ideOption.isVisible({ timeout: 5000 }).catch(() => false))
...
-    const cloudIdeHeading = page.locator('h3', { hasText: '使用WeCode云IDE' }).first()
-    const isCloudIdeVisible = await cloudIdeHeading.isVisible({ timeout: 3000 }).catch(() => false)
+    const isCloudIdeVisible = await cloudIdeOption.isVisible({ timeout: 3000 }).catch(() => false)
...
-        await cloudIdeHeading.click({ timeout: 2000 })
+        await cloudIdeOption.click({ timeout: 2000 })
...
-        await page.getByText('在IDE中使用WeCode').first().click({ timeout: 2000 })
+        await ideOption.click({ timeout: 2000 })
Based on learnings, "Use `data-testid` attributes for locating page elements in E2E tests; prefer this over text content, CSS classes, or complex XPath selectors."

Also applies to: 65-129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/specs/code-flow.spec.ts` around lines 58 - 60, The test is locating the
onboarding dialog by localized heading text via onboardingHeading =
page.locator('h2', { hasText: ... }) which is brittle; update the locator to use
the stable data-testid attributes for the onboarding dialog/cards (replace the
page.locator call that sets onboardingHeading and any similar locators in the
65-129 range) so tests target the onboarding test IDs instead of localized copy
or XPath; ensure you update all occurrences (the onboardingHeading variable and
any other page.locator references for onboarding elements) to use the
appropriate data-testid strings provided in the PR.


if (isOnboardingVisible) {
console.log('✓ Code feature onboarding page detected, selecting cloud IDE option...')

// Click the "使用WeCode云IDE" (Use WeCode Cloud IDE) option
// Find by the h3 heading and click on its parent card
const cloudIdeHeading = page.locator('h3', { hasText: '使用WeCode云IDE' }).first()
const isCloudIdeVisible = await cloudIdeHeading.isVisible({ timeout: 3000 }).catch(() => false)

if (isCloudIdeVisible) {
// Strategy: Find the clickable parent element of the h3 heading
// Try multiple approaches to find the actual clickable card
let clicked = false

// Strategy 1: Try clicking the heading itself (it might have click handler)
try {
await cloudIdeHeading.click({ timeout: 2000 })
console.log('✓ Clicked: 使用WeCode云IDE (heading)')
clicked = true
} catch {
Comment on lines +73 to +80
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast if the onboarding dialog is still open.

Right now this branch sleeps for 3 seconds and logs completion even when every click attempt falls through. Throw when no option was clicked and explicitly wait for the onboarding UI to disappear; otherwise the next clicks can still be intercepted by the modal.

✅ Suggested change
   if (isOnboardingVisible) {
     console.log('✓ Code feature onboarding page detected, selecting cloud IDE option...')
+    let clicked = false

     // Click the "使用WeCode云IDE" (Use WeCode Cloud IDE) option
     // Find by the h3 heading and click on its parent card
     const cloudIdeHeading = page.locator('h3', { hasText: '使用WeCode云IDE' }).first()
     const isCloudIdeVisible = await cloudIdeHeading.isVisible({ timeout: 3000 }).catch(() => false)

     if (isCloudIdeVisible) {
       // Strategy: Find the clickable parent element of the h3 heading
       // Try multiple approaches to find the actual clickable card
-      let clicked = false
-
       // Strategy 1: Try clicking the heading itself (it might have click handler)
       try {
         await cloudIdeHeading.click({ timeout: 2000 })
         console.log('✓ Clicked: 使用WeCode云IDE (heading)')
         clicked = true
@@
     } else {
       // Fallback: try the first option (在IDE中使用WeCode)
       try {
         await page.getByText('在IDE中使用WeCode').first().click({ timeout: 2000 })
         console.log('✓ Clicked: 在IDE中使用WeCode')
+        clicked = true
       } catch {
         console.log('✗ Could not click any onboarding option')
       }
     }

-    // Wait for the onboarding page to disappear
-    await page.waitForTimeout(3000)
+    if (!clicked) {
+      throw new Error('Could not dismiss the code-feature onboarding')
+    }
+    await expect(onboardingHeading).toBeHidden({ timeout: 10000 })

     console.log('✓ Onboarding completed, continuing with test...')
   }
Based on learnings, "Use appropriate timeouts and visibility checks when waiting for elements."

Also applies to: 123-135

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/specs/code-flow.spec.ts` around lines 73 - 80, The test currently tries
multiple click strategies on cloudIdeHeading but sleeps on failure; instead,
after the click attempts using cloudIdeHeading and subsequent strategies (the
clicked flag), if clicked is still false throw an error to fail fast and call a
visibility wait to ensure the onboarding modal/dialog is gone (e.g., await
page.waitForSelector('<onboarding-selector>', { state: 'hidden', timeout: 5000
}) or use the existing onboarding dialog locator) before proceeding so
subsequent clicks aren't intercepted; update the block around cloudIdeHeading
and the similar block at lines 123-135 to throw when no option was clicked and
to explicitly wait for the onboarding UI to disappear.

// Continue to next strategy
}

// Strategy 2: Use XPath to find parent button
if (!clicked) {
try {
const parentButton = page.locator('h3:has-text("使用WeCode云IDE") >> xpath=ancestor::button[1]')
if (await parentButton.isVisible({ timeout: 2000 })) {
await parentButton.click()
console.log('✓ Clicked: 使用WeCode云IDE (parent button)')
clicked = true
}
} catch {
// Continue to next strategy
}
}

// Strategy 3: Use XPath to find nearest div with border/card styling
if (!clicked) {
try {
const cardDiv = page.locator('h3:has-text("使用WeCode云IDE") >> xpath=ancestor::div[contains(@class, "border") or contains(@class, "cursor-pointer")][1]')
if (await cardDiv.isVisible({ timeout: 2000 })) {
await cardDiv.click()
console.log('✓ Clicked: 使用WeCode云IDE (card div)')
clicked = true
}
} catch {
// Continue to next strategy
}
}

// Strategy 4: Click any element with the text
if (!clicked) {
try {
await page.getByText('使用WeCode云IDE').first().click({ timeout: 2000 })
console.log('✓ Clicked: 使用WeCode云IDE (getByText)')
clicked = true
} catch {
console.log('✗ Failed to click Cloud IDE option')
}
}
} else {
// Fallback: try the first option (在IDE中使用WeCode)
try {
await page.getByText('在IDE中使用WeCode').first().click({ timeout: 2000 })
console.log('✓ Clicked: 在IDE中使用WeCode')
} catch {
console.log('✗ Could not click any onboarding option')
}
}

// Wait for the onboarding page to disappear
await page.waitForTimeout(3000)

console.log('✓ Onboarding completed, continuing with test...')
} else {
console.log('✓ No onboarding page detected, continuing...')
}

// Step 1: Select dev-team agent
console.log('Selecting dev-team agent...')
const teamSelector = page.locator('[data-testid="team-selector"]').first()
await expect(teamSelector).toBeVisible({ timeout: 10000 })

// Click to open team selector dropdown
await teamSelector.click()
await page.waitForTimeout(500)
await page.waitForTimeout(1000)

// Search for dev-team
const searchInput = page.locator('input[placeholder*="搜索"], input[placeholder*="Search"]').first()
if (await searchInput.isVisible({ timeout: 3000 }).catch(() => false)) {
await searchInput.fill('dev-team')
await page.waitForTimeout(500)
await page.waitForTimeout(1000)
console.log('Filled team search input')
}

// Select dev-team from dropdown
// Select dev-team from dropdown - wait for it to appear
const devTeamOption = page.locator('[data-testid="team-option-dev-team"]').first()
if (await devTeamOption.isVisible({ timeout: 3000 }).catch(() => false)) {
try {
await expect(devTeamOption).toBeVisible({ timeout: 5000 })
await devTeamOption.click()
} else {
console.log('Selected dev-team agent')
} catch {
// Try to find by text content
const teamOptions = page.locator('[data-testid^="team-option-"]').filter({ hasText: /dev-team/i })
if (await teamOptions.count() > 0) {
await teamOptions.first().click()
console.log('Could not find exact team option, trying fallback...')
const teamOptions = page.locator('[data-testid^="team-option-"]')
const count = await teamOptions.count()
console.log(`Found ${count} team options`)
if (count > 0) {
// Find first option containing dev-team
for (let i = 0; i < count; i++) {
const text = await teamOptions.nth(i).textContent()
console.log(`Team option ${i}: ${text}`)
if (text && text.toLowerCase().includes('dev-team')) {
await teamOptions.nth(i).click()
console.log('Selected dev-team by text match')
break
}
}
}
}
await page.waitForTimeout(500)
await page.waitForTimeout(1000)

// Step 2: Select repository wecode-ai/Wegent
// Step 2: Select repository weibo_rd/common/wecode/wegent
console.log('Selecting repository...')
const repoSelector = page.locator('[data-testid="repo-branch-selector"]').first()
await expect(repoSelector).toBeVisible({ timeout: 10000 })

// Click to open repository selector
await repoSelector.click()
await page.waitForTimeout(500)
await page.waitForTimeout(1000)

// Wait for the repository dropdown to be visible
const repoDropdown = page.locator('[data-testid="repo-branch-selector-dropdown"]').first()
if (await repoDropdown.isVisible({ timeout: 5000 }).catch(() => false)) {
console.log('Repository dropdown opened')
}

// Search for repository
const repoSearchInput = page.locator('input[placeholder*="仓库"], input[placeholder*="repository"], input[placeholder*="搜索"]').first()
if (await repoSearchInput.isVisible({ timeout: 3000 }).catch(() => false)) {
await repoSearchInput.fill('wecode-ai/Wegent')
await page.waitForTimeout(500)
await repoSearchInput.fill('wegent')
await page.waitForTimeout(1000)
console.log('Filled repository search input')
}

// Select wecode-ai/Wegent repository
const wegentRepoOption = page.locator('[data-testid="repo-option-wecode-ai-Wegent"]').first()
if (await wegentRepoOption.isVisible({ timeout: 3000 }).catch(() => false)) {
// Select weibo_rd/common/wecode/wegent repository - wait for it to appear after search
// Note: data-testid replaces / with -
const wegentRepoOption = page.locator('[data-testid="repo-option-weibo_rd-common-wecode-wegent"]').first()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Select the documented public repo in setup

This hard-codes repo-option-weibo_rd-common-wecode-wegent, but the test suite still documents and expects wecode-ai/Wegent for code-flow runs. In environments that only expose the documented public repo, this locator never resolves, so setup cannot pick a repository and the test fails before validating any code-flow behavior. Please keep the canonical repo target (or make it configurable per environment) instead of pinning to an internal-only repo id.

Useful? React with 👍 / 👎.

try {
await expect(wegentRepoOption).toBeVisible({ timeout: 5000 })
await wegentRepoOption.click()
} else {
console.log('Selected weibo_rd/common/wecode/wegent repository')
} catch {
// Try to find by text content
const repoOptions = page.locator('[data-testid^="repo-option-"]').filter({ hasText: /wecode-ai/i })
if (await repoOptions.count() > 0) {
await repoOptions.first().click()
console.log('Could not find exact repo option, trying fallback...')
const repoOptions = page.locator('[data-testid^="repo-option-"]')
const count = await repoOptions.count()
console.log(`Found ${count} repo options`)
if (count > 0) {
// Find first option containing wegent
for (let i = 0; i < count; i++) {
const text = await repoOptions.nth(i).textContent()
console.log(`Repo option ${i}: ${text}`)
if (text && text.includes('wegent')) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match fallback repo names without case sensitivity

The fallback filter uses text.includes('wegent'), which is case-sensitive. If the option text is .../Wegent (capital W), the fallback path won't select any repository when the exact data-testid lookup misses, and setup proceeds with no repo selected. Converting both sides to lowercase before matching avoids this brittle failure mode.

Useful? React with 👍 / 👎.

await repoOptions.nth(i).click()
console.log('Selected repository by text match')
break
}
Comment on lines +217 to +229
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Assert the exact repo/branch here, not just “something got selected”.

The fallback picks the first option whose text contains wegent, which is ambiguous when sibling nested repos exist; backend/app/services/git_skill/utils.py:108-113 already shows .../wegent-skills-style paths. Then the final guard only rejects Chinese placeholder text, so the test can keep running against the wrong repo/branch or an English placeholder state.

🧭 Suggested change
-        if (text && text.includes('wegent')) {
+        if (text?.trim() === 'weibo_rd/common/wecode/wegent') {
           await repoOptions.nth(i).click()
           console.log('Selected repository by text match')
           break
         }
...
-  if (!repoBranchText || repoBranchText.includes('请选择') || repoBranchText.includes('选择')) {
-    throw new Error('Repository was not properly selected. Selector text: ' + repoBranchText)
+  const normalizedRepoBranchText = repoBranchText?.replace(/\s+/g, ' ').trim() ?? ''
+  if (
+    !normalizedRepoBranchText.includes('weibo_rd/common/wecode/wegent') ||
+    !normalizedRepoBranchText.includes('main')
+  ) {
+    throw new Error(`Repository/branch selection mismatch: ${normalizedRepoBranchText}`)
   }
frontend/src/features/tasks/components/selector/RepoListView.tsx:118-130 already exposes the full repo label in the repo option `data-testid`, so the exact match is available here.

Also applies to: 284-287

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/specs/code-flow.spec.ts` around lines 217 - 229, The test currently
selects the first repo option whose visible text contains 'wegent', which is
ambiguous; change selection to match the exact data-testid value exposed by
RepoListView (the full repo label present in the repo option data-testid)
instead of using text.includes. Locate the repoOptions locator and replace the
fuzzy match loop (using textContent/includes 'wegent') with a check against the
exact expected data-testid (e.g., compare
repoOptions.nth(i).getAttribute('data-testid') to the known full repo label) and
click when equal; then assert the selected repo/branch equals that exact
expected label (also update the analogous logic at lines ~284-287) so the test
fails if a sibling nested repo is chosen.

}
}
}
await page.waitForTimeout(500)
await page.waitForTimeout(1000)

// Verify repository is selected by checking the selector text
const repoSelectorText = await repoSelector.textContent()
Expand All @@ -113,32 +240,52 @@ async function setupCodePage(page: any) {
console.log('Selecting branch...')
// Click to open repo selector again to access branch selection
await repoSelector.click()
await page.waitForTimeout(500)
await page.waitForTimeout(1000)

// Search for main branch
const branchSearchInput = page.locator('input[placeholder*="分支"], input[placeholder*="branch"]').first()
if (await branchSearchInput.isVisible({ timeout: 3000 }).catch(() => false)) {
await branchSearchInput.fill('main')
await page.waitForTimeout(500)
await page.waitForTimeout(1000)
console.log('Filled branch search input')
}

// Select main branch
// Select main branch - wait for it to appear
const mainBranchOption = page.locator('[data-testid="branch-option-main"]').first()
if (await mainBranchOption.isVisible({ timeout: 3000 }).catch(() => false)) {
try {
await expect(mainBranchOption).toBeVisible({ timeout: 5000 })
await mainBranchOption.click()
} else {
console.log('Selected main branch')
} catch {
// Try to find by text content
const branchOptions = page.locator('[data-testid^="branch-option-"]').filter({ hasText: /^main$/ })
if (await branchOptions.count() > 0) {
await branchOptions.first().click()
console.log('Could not find exact branch option, trying fallback...')
const branchOptions = page.locator('[data-testid^="branch-option-"]')
const count = await branchOptions.count()
console.log(`Found ${count} branch options`)
if (count > 0) {
// Find first option that is exactly "main"
for (let i = 0; i < count; i++) {
const text = await branchOptions.nth(i).textContent()
console.log(`Branch option ${i}: ${text}`)
if (text && text.trim() === 'main') {
await branchOptions.nth(i).click()
console.log('Selected main branch by text match')
break
}
}
}
}
await page.waitForTimeout(500)
await page.waitForTimeout(1000)

// Verify repository and branch are selected
const repoBranchText = await repoSelector.textContent()
console.log('Repository/Branch selected:', repoBranchText)

// Verify that a repository is actually selected (not the placeholder)
if (!repoBranchText || repoBranchText.includes('请选择') || repoBranchText.includes('选择')) {
throw new Error('Repository was not properly selected. Selector text: ' + repoBranchText)
}

// Step 4: Select model 公网:GLM-5
console.log('Selecting model...')
const modelSelector = page.locator('[data-testid="model-selector"]').first()
Expand Down
Loading