Skip to content
Open
Show file tree
Hide file tree
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
46 changes: 41 additions & 5 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: E2E Tests
on:
on:
pull_request:
types: [opened, synchronize, reopened]

jobs:
e2e-tests:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -21,14 +21,50 @@ jobs:
checkInterval: 10
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


- name: Network diagnostics for deploy preview
run: |
echo "## 🌐 Network Diagnostics" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Testing connectivity to Netlify deploy preview..." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

DEPLOY_URL="https://deploy-preview-${{ github.event.pull_request.number }}--leafy-mooncake-7c2e5e.netlify.app"

echo "### Deploy Preview URL" >> $GITHUB_STEP_SUMMARY
echo "\`$DEPLOY_URL\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

# Test DNS resolution
echo "### DNS Resolution" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
nslookup deploy-preview-${{ github.event.pull_request.number }}--leafy-mooncake-7c2e5e.netlify.app || echo "DNS lookup failed" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

# Test HTTP response time and headers
echo "### HTTP Response Test" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
time curl -I -L -s -w "\nHTTP Code: %{http_code}\nTime Total: %{time_total}s\nTime Connect: %{time_connect}s\nTime Start Transfer: %{time_starttransfer}s\n" "$DEPLOY_URL" || echo "HTTP request failed"
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

# Test navigation API endpoint
echo "### Navigation API Test" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
time curl -s -w "\nHTTP Code: %{http_code}\nTime Total: %{time_total}s\n" "$DEPLOY_URL/api/navigation" | head -20
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY

- name: Run Cypress E2E tests
uses: cypress-io/github-action@v6
with:
wait-on: 'https://deploy-preview-${{ github.event.pull_request.number }}--leafy-mooncake-7c2e5e.netlify.app'
wait-on-timeout: 120
build: echo "Skipping build - testing against Netlify preview"
env:
CYPRESS_baseUrl: https://deploy-preview-${{ github.event.pull_request.number }}--leafy-mooncake-7c2e5e.netlify.app
DEBUG: 'cypress:server:*'
ELECTRON_ENABLE_LOGGING: 1

- name: Upload Cypress screenshots on failure
if: failure()
Expand All @@ -53,13 +89,13 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Deployment URL:** https://deploy-preview-${{ github.event.pull_request.number }}--leafy-mooncake-7c2e5e.netlify.app" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

if [[ "${{ job.status }}" == "success" ]]; then
echo "✅ All tests passed!" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Some tests failed. Check the logs for details." >> $GITHUB_STEP_SUMMARY
fi

if [[ -f "./cypress.log" ]]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY
Expand Down
9 changes: 8 additions & 1 deletion cypress.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ module.exports = defineConfig({
specPattern: 'src/tests/cypress/integration/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'src/tests/cypress/support/index.js',
baseUrl: 'http://localhost:3030',
pageLoadTimeout: 120000, // Increase timeout to 120 seconds for Netlify preview
pageLoadTimeout: 30000, // Fail fast - max 30 seconds for page load
defaultCommandTimeout: 10000, // 10 seconds for commands
requestTimeout: 10000, // 10 seconds for resource requests - fail faster on network issues
responseTimeout: 10000, // 10 seconds for response - fail faster
retries: {
runMode: 2, // Retry failed tests 2 times in CI
openMode: 0, // No retries in interactive mode
},
},
})
78 changes: 70 additions & 8 deletions src/tests/cypress/integration/copy-for-llm.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,35 @@ describe('Copy for AI Feature', () => {
? tutorialsSection.slugPrefix.slice(0, -1)
: tutorialsSection.slugPrefix
tutorialUrl = `${prefix}/${slug}`
cy.log(`Using tutorial: ${tutorialUrl}`)
cy.log(`📄 Selected tutorial: ${tutorialUrl}`)
cy.log(
`Language slugs - EN: ${tutorialSlugs.en}, ES: ${tutorialSlugs.es}, PT: ${tutorialSlugs.pt}`
`🌐 Language slugs - EN: ${tutorialSlugs.en}, ES: ${tutorialSlugs.es}, PT: ${tutorialSlugs.pt}`
)

// Write to cypress log file for CI visibility
cy.writeFile(
'cypress.log',
`Selected tutorial URL: ${tutorialUrl}\n`,
{
flag: 'a+',
}
)
cy.writeFile('cypress.log', `EN slug: ${tutorialSlugs.en}\n`, {
flag: 'a+',
})
cy.writeFile(
'cypress.log',
`ES slug: ${tutorialSlugs.es || 'N/A'}\n`,
{
flag: 'a+',
}
)
cy.writeFile(
'cypress.log',
`PT slug: ${tutorialSlugs.pt || 'N/A'}\n\n`,
{
flag: 'a+',
}
)
} else {
throw new Error('No markdown tutorial found in navigation')
Expand All @@ -79,11 +105,15 @@ describe('Copy for AI Feature', () => {
beforeEach(() => {
// Handle hydration errors globally for all tests
cy.on('uncaught:exception', (err) => {
// Log all uncaught exceptions for debugging
cy.log(`⚠️ Uncaught exception: ${err.message}`)

// Ignore hydration-related errors
if (
err.message.includes('Suspense boundary') ||
err.message.includes('hydrating')
) {
cy.log(`ℹ️ (Ignoring hydration error)`)
return false
}
return true
Expand All @@ -99,14 +129,34 @@ describe('Copy for AI Feature', () => {
})

it('Should find the Copy for AI button in English', () => {
cy.visit(tutorialUrl)
const startTime = Date.now()
cy.log(`🔗 Visiting: ${tutorialUrl}`)

cy.visit(tutorialUrl, {
timeout: 30000,
failOnStatusCode: false,
}).then(
() => {
const loadTime = Date.now() - startTime
cy.log(`✅ Page loaded in ${loadTime}ms`)
},
(err) => {
const loadTime = Date.now() - startTime
cy.log(`❌ Visit failed after ${loadTime}ms: ${err.message}`)
throw err
}
)

cy.contains('button', 'Copy for AI', { timeout: 10000 }).should(
'be.visible'
)
})

it('Should copy content to clipboard in English and verify', () => {
cy.visit(tutorialUrl)
cy.visit(tutorialUrl, {
timeout: 30000,
failOnStatusCode: false,
})

// Stub document.execCommand to capture copy operations
cy.document().then((doc) => {
Expand Down Expand Up @@ -149,7 +199,10 @@ describe('Copy for AI Feature', () => {
})

it('Should switch to Spanish and find Copy for AI button', () => {
cy.visit(tutorialUrl)
cy.visit(tutorialUrl, {
timeout: 30000,
failOnStatusCode: false,
})

// Click language switcher
cy.contains('button', 'EN').click()
Expand All @@ -176,7 +229,10 @@ describe('Copy for AI Feature', () => {
tutorialSlugs.es
}`

cy.visit(spanishUrl)
cy.visit(spanishUrl, {
timeout: 30000,
failOnStatusCode: false,
})

// Wait for page to be fully loaded and hydrated
cy.get('article', { timeout: 15000 }).should('be.visible')
Expand Down Expand Up @@ -229,7 +285,10 @@ describe('Copy for AI Feature', () => {
})

it('Should switch to Portuguese and find Copy for AI button', () => {
cy.visit(tutorialUrl)
cy.visit(tutorialUrl, {
timeout: 30000,
failOnStatusCode: false,
})

// Click language switcher
cy.contains('button', 'EN').click()
Expand All @@ -256,7 +315,10 @@ describe('Copy for AI Feature', () => {
tutorialSlugs.pt
}`

cy.visit(portugueseUrl)
cy.visit(portugueseUrl, {
timeout: 30000,
failOnStatusCode: false,
})

// Wait for page to be fully loaded and hydrated
cy.get('article', { timeout: 15000 }).should('be.visible')
Expand Down
27 changes: 26 additions & 1 deletion src/tests/cypress/integration/helpcenter-navigation-status.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,17 @@ describe('Help Center Navigation Status Test', () => {
it('should successfully load randomly selected pages from each navbar section', () => {
// Handle hydration and React errors during page loads
cy.on('uncaught:exception', (err) => {
// Log all uncaught exceptions for debugging
cy.task('log', ` ⚠️ Uncaught exception: ${err.message}`)

// Ignore hydration-related errors and React minified errors
if (
err.message.includes('Suspense boundary') ||
err.message.includes('hydrating') ||
err.message.includes('Minified React error') ||
err.message.includes('invariant')
) {
cy.task('log', ` ℹ️ (Ignoring hydration/React error)`)
return false
}
return true
Expand All @@ -111,7 +115,28 @@ describe('Help Center Navigation Status Test', () => {
cy.task('log', ` Page: "${page.name}"`)
cy.task('log', ` URL: ${page.url}`)

cy.visit(page.url, { failOnStatusCode: false })
const startTime = Date.now()

cy.visit(page.url, {
timeout: 30000,
failOnStatusCode: false,
onBeforeLoad: () => {
cy.task('log', ` ⏱️ Page load started`)
},
}).then(
() => {
const totalTime = Date.now() - startTime
cy.task('log', ` ⏱️ Visit completed in ${totalTime}ms`)
},
(err) => {
const totalTime = Date.now() - startTime
cy.task(
'log',
` ❌ Visit failed after ${totalTime}ms: ${err.message}`
)
throw err
}
)

// Verify page loads successfully
cy.url().should('include', page.url.replace(baseUrl, ''))
Expand Down
Loading