Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BRIDGE_BASE_ADDRESS=https://console-openshift-console.apps.your-cluster.example.com
BRIDGE_KUBEADMIN_PASSWORD=your-password
TEST_NS=cy-test-ns
UDN_NS=udn-test-ns
HIDE_XHR=true
50 changes: 50 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: E2E Tests

on:
workflow_dispatch:
inputs:
reason:
description: 'Reason for manual run'
required: false
type: string

concurrency:
group: e2e-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

jobs:
e2e:
name: Cypress E2E
runs-on: ubuntu-latest
timeout-minutes: 30

env:
BRIDGE_BASE_ADDRESS: ${{ secrets.CONSOLE_URL }}
BRIDGE_KUBEADMIN_PASSWORD: ${{ secrets.KUBEADMIN_PASSWORD }}
BRIDGE_E2E_BROWSER_NAME: electron
TEST_NS: ${{ secrets.TEST_NS || 'cy-test-ns' }}
UDN_NS: udn-test-ns

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Run E2E tests
run: npm run test-e2e

- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-results
path: ui-tests-cy/gui-test-screenshots/
retention-days: 7
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ integration-tests/screenshots
integration-tests/.DS_Store
yarn-error.log
.DS_Store
cypress/gui-test-screenshots/
.env
22 changes: 22 additions & 0 deletions cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

# Cleanup test resources from previous runs
# Sources .env for namespace names

source .env 2>/dev/null || true
TEST_NS="${TEST_NS:-cy-test-ns}"
UDN_NS="${UDN_NS:-udn-test-ns}"

cleanup () {
echo "Cleaning up test resources..."
oc delete --ignore-not-found=true -n ${TEST_NS} net-attach-def network-bridge network-ovn network-localnet --wait=false
oc delete --ignore-not-found=true -n ${TEST_NS} service example --wait=false
oc delete --ignore-not-found=true -n ${TEST_NS} route auto-test-route --wait=false
oc delete --ignore-not-found=true -n ${TEST_NS} ingress example --wait=false
oc delete --ignore-not-found=true -n ${TEST_NS} networkpolicy auto-test-net-policy --wait=true --timeout=60s
oc delete --ignore-not-found=true -n ${TEST_NS} multi-networkpolicy auto-test-multi-policy --wait=false
oc delete --ignore-not-found=true ClusterUserDefinedNetwork cluster-udn --wait=false
oc delete --ignore-not-found=true -n ${UDN_NS} UserDefinedNetwork primary-udn --wait=false
oc delete nncp --all --ignore-not-found --wait=false
echo "Cleanup done."
}
4 changes: 2 additions & 2 deletions integration-tests/support/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ declare global {
}

const KUBEADMIN_USERNAME = 'kubeadmin';
const loginUsername = Cypress.env('BRIDGE_KUBEADMIN_PASSWORD') ? 'user-dropdown' : 'username';
const loginUsername = cy.env('BRIDGE_KUBEADMIN_PASSWORD') ? 'user-dropdown' : 'username';

// This will add 'cy.login(...)'
// ex: cy.login('my-user', 'my-password')
Expand All @@ -24,7 +24,7 @@ Cypress.Commands.add('login', (username: string, password: string) => {
cy.clearCookie('openshift-session-token');

cy.get('#inputUsername').type(username || KUBEADMIN_USERNAME);
cy.get('#inputPassword').type(password || Cypress.env('BRIDGE_KUBEADMIN_PASSWORD'));
cy.get('#inputPassword').type(cy.env('BRIDGE_KUBEADMIN_PASSWORD') || password);
cy.get('button[type=submit]').click();

cy.get(`[data-test="${loginUsername}"]`).should('be.visible');
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/tests/example-page.cy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { checkErrors } from '../support';

const PLUGIN_TEMPLATE_NAME = 'networking-console-plugin';
const PLUGIN_TEMPLATE_PULL_SPEC = Cypress.env('PLUGIN_TEMPLATE_PULL_SPEC');
const PLUGIN_TEMPLATE_PULL_SPEC = Cypress.expose('PLUGIN_TEMPLATE_PULL_SPEC');
export const isLocalDevEnvironment = Cypress.config('baseUrl').includes('localhost');

const installHelmChart = (path: string) => {
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
"start-console": "./start-console.sh",
"i18n": "./i18n-scripts/build-i18n.sh && node ./i18n-scripts/set-english-defaults.js",
"ts-node": "ts-node -O '{\"module\":\"commonjs\"}'",
"lint": "eslint ./src ./integration-tests",
"lint-fix": "eslint ./src ./integration-tests --fix",
"test-cypress": "cd integration-tests s && cypress open --env openshift=true",
"lint": "eslint ./src ./integration-tests ./cypress",
"lint-fix": "eslint ./src ./integration-tests ./cypress --fix",
"test-cypress": "cd integration-tests && cypress open --env openshift=true",
"test-cypress-headless": "cd integration-tests && node --max-old-space-size=4096 ../node_modules/.bin/cypress run --env openshift=true --browser ${BRIDGE_E2E_BROWSER_NAME:=electron}",
"cypress-merge": "mochawesome-merge ./integration-tests/screenshots/cypress_report*.json > ./integration-tests/screenshots/cypress.json",
"cypress-generate": "marge -o ./integration-tests/screenshots/ -f cypress-report -t 'OpenShift Console Plugin Template Cypress Test Results' -p 'OpenShift Cypress Plugin Template Test Results' --showPassed false --assetsDir ./integration-tests/screenshots/cypress/assets ./integration-tests/screenshots/cypress.json",
"test-e2e": "cd ui-tests-cy && node --max-old-space-size=4096 ../node_modules/.bin/cypress run --env openshift=true --browser ${BRIDGE_E2E_BROWSER_NAME:=electron}",
"test-e2e-open": "cd ui-tests-cy && cypress open --env openshift=true",
"cypress-merge": "mochawesome-merge ./ui-tests-cy/gui-test-screenshots/cypress_report*.json > ./ui-tests-cy/gui-test-screenshots/cypress.json",
"cypress-generate": "marge -o ./ui-tests-cy/gui-test-screenshots/ -f cypress-report -t 'Networking Console Plugin Test Results' -p 'Networking Plugin Test Results' --showPassed false --assetsDir ./ui-tests-cy/gui-test-screenshots/assets ./ui-tests-cy/gui-test-screenshots/cypress.json",
"cypress-postreport": "npm run cypress-merge && npm run cypress-generate",
"memsource-upload": "./i18n-scripts/memsource-upload.sh",
"memsource-download": "./i18n-scripts/memsource-download.sh",
Expand Down Expand Up @@ -58,6 +60,7 @@
"css-loader": "^6.7.1",
"cypress": "^15.0.0",
"cypress-multi-reporters": "^2.0.0",
"dotenv": "^17.4.2",
"esbuild-loader": "^4.4.2",
"eslint": "^8.57.1",
"eslint-config-prettier": "^8.10.0",
Expand All @@ -79,8 +82,8 @@
"pluralize": "^8.0.0",
"prettier": "^3.2.5",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "18.3.1",
"react-i18next": "~16.5.8",
"react-router": "~7.13.1",
"resolve-url-loader": "^5.0.0",
Expand Down
120 changes: 120 additions & 0 deletions research-flakiness.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env bash

set -x
set +e

PROBLEM_DIR="problem"
TOTAL_RUNS=10
PASS_COUNT=0
FAIL_COUNT=0

mkdir -p "${PROBLEM_DIR}"

for i in $(seq -w 1 ${TOTAL_RUNS}); do
echo "========================================"
echo " RUN ${i} of ${TOTAL_RUNS}"
echo "========================================"

# Clean previous test artifacts
rm -rf cypress/gui-test-screenshots/*

# Run the test suite
./test-cypress.sh
exit_code=$?

if [ ${exit_code} -ne 0 ]; then
FAIL_COUNT=$((FAIL_COUNT + 1))
RUN_DIR="${PROBLEM_DIR}/run${i}"
mkdir -p "${RUN_DIR}"

# Copy screenshots and videos
if [ -d "cypress/gui-test-screenshots/screenshots" ]; then
cp -r cypress/gui-test-screenshots/screenshots "${RUN_DIR}/"
fi
if [ -d "cypress/gui-test-screenshots/videos" ]; then
cp -r cypress/gui-test-screenshots/videos "${RUN_DIR}/"
fi
if [ -f "cypress/gui-test-screenshots/build.log" ]; then
cp cypress/gui-test-screenshots/build.log "${RUN_DIR}/"
fi

# Extract failure info into analysis.md
cat > "${RUN_DIR}/analysis.md" <<ANALYSIS
# Run ${i} — FAILED

## Failing tests

$(grep -E "^\s+\d\)" cypress/gui-test-screenshots/build.log 2>/dev/null || echo "Could not extract test names")

## Error messages

$(grep -A5 "^\s+\d\) " cypress/gui-test-screenshots/build.log 2>/dev/null | head -60 || echo "Could not extract errors")

## Observations

- The NAD creation form uses react-hook-form with auto-generated names
- The naive approach (clear + type without waits) races with React re-renders
- The form may overwrite user input after async state updates

ANALYSIS

echo " -> FAILED (evidence saved to ${RUN_DIR}/)"
else
PASS_COUNT=$((PASS_COUNT + 1))
echo " -> PASSED"
fi
done

echo ""
echo "========================================"
echo " RESULTS: ${PASS_COUNT} passed, ${FAIL_COUNT} failed out of ${TOTAL_RUNS} runs"
echo "========================================"

# Write summary
cat > "${PROBLEM_DIR}/summary.md" <<SUMMARY
# NAD Form Flakiness Research — Summary

## Results

- **Total runs:** ${TOTAL_RUNS}
- **Passed:** ${PASS_COUNT}
- **Failed:** ${FAIL_COUNT}
- **Failure rate:** $((FAIL_COUNT * 100 / TOTAL_RUNS))%

## Problem Description

The NetworkAttachmentDefinition (NAD) creation form in the networking-console-plugin
uses \`react-hook-form\` which auto-generates a random name on mount. When Cypress
types into the name field immediately after the form renders, React's asynchronous
state updates can overwrite the typed value with the auto-generated name.

### Root Cause

The form component calls \`register('name', { required: true })\` which creates a
controlled input. On initial render (and on network-type selection), the form state
resets and generates a new random name. If Cypress types before this reset completes,
the typed value is lost.

### Affected Tests

- NAD Bridge creation (name overwritten by auto-generated value)
- NAD Localnet creation (same issue)
- NAD OVN/L2 overlay creation (same issue, plus submit button stays disabled)

### Evidence

Each \`runXX/\` folder contains:
- \`screenshots/\` — failure screenshots showing the form state at time of failure
- \`videos/\` — full test recording
- \`build.log\` — Cypress console output
- \`analysis.md\` — per-run failure details

### Recommended Fix (for developers)

1. Remove the auto-generated name from the form initial state, OR
2. Add a \`data-test-ready\` attribute after the form has fully initialized, so tests can wait for it, OR
3. Debounce/stabilize the form state before allowing user input

SUMMARY

echo "Summary written to ${PROBLEM_DIR}/summary.md"
43 changes: 43 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash

# Setup test namespaces (create if they don't exist)
# Sources .env for namespace names

source .env 2>/dev/null || true
TEST_NS="${TEST_NS:-cy-test-ns}"
UDN_NS="${UDN_NS:-udn-test-ns}"
UDN_LABEL="k8s.ovn.org/primary-user-defined-network"

setup () {
echo "Setting up test namespaces..."
oc get namespace ${TEST_NS} 2>/dev/null || oc create namespace ${TEST_NS}

# UDN namespace must have the label at creation time (admission policy prevents adding later)
if oc get namespace ${UDN_NS} 2>/dev/null; then
if ! oc get namespace ${UDN_NS} -o jsonpath='{.metadata.labels}' | grep -q "${UDN_LABEL}"; then
echo "UDN namespace exists but missing label, recreating..."
oc delete namespace ${UDN_NS} --wait=true --timeout=120s
oc wait --for=delete namespace/${UDN_NS} --timeout=120s 2>/dev/null || true
oc create -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: ${UDN_NS}
labels:
${UDN_LABEL}: ""
EOF
else
echo "UDN namespace exists with proper label."
fi
else
oc create -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: ${UDN_NS}
labels:
${UDN_LABEL}: ""
EOF
fi
echo "Setup done."
}
38 changes: 38 additions & 0 deletions test-cypress.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash

set -x
set +e

source ./cleanup.sh
source ./setup.sh

while getopts g:s: flag
do
case "${flag}" in
g) gui=${OPTARG};;
s) spec=${OPTARG};;
esac
done

if [ -z "${spec-}" ]; then
spec="tests/all.cy.ts"
fi

# Run cleanup and setup before tests
cleanup
setup

mkdir -p cypress/gui-test-screenshots

if [ -n "${gui-}" ]; then
cd cypress && ../node_modules/.bin/cypress open --env openshift=true --spec "$spec"
else
cd cypress && node --max-old-space-size=4096 ../node_modules/.bin/cypress run --env openshift=true --browser ${BRIDGE_E2E_BROWSER_NAME:=electron} --spec "$spec" | tee ./gui-test-screenshots/build.log
test_exit_code=${PIPESTATUS[0]}
cd ..
npm run cypress-postreport

if [ ${test_exit_code} -eq 0 ]; then
cleanup
fi
fi
Loading