|
| 1 | +name: E2E Tests |
| 2 | +on: |
| 3 | + workflow_call: |
| 4 | + secrets: |
| 5 | + CONNECT_LICENSE: |
| 6 | + required: true |
| 7 | + |
| 8 | +jobs: |
| 9 | + cypress: |
| 10 | + name: e2e tests (cypress) |
| 11 | + runs-on: ubuntu-latest |
| 12 | + env: |
| 13 | + DEBUG_CYPRESS: "false" # Set to true to enable extra logging and video recording replays |
| 14 | + steps: |
| 15 | + - name: Checkout code |
| 16 | + uses: actions/checkout@v3 |
| 17 | + with: |
| 18 | + fetch-depth: 0 |
| 19 | + |
| 20 | + - name: Set up Python |
| 21 | + uses: actions/setup-python@v5 |
| 22 | + with: |
| 23 | + python-version: "3.11" |
| 24 | + cache: pip |
| 25 | + cache-dependency-path: test/e2e/requirements.txt |
| 26 | + |
| 27 | + - name: Set up Node.js |
| 28 | + uses: actions/setup-node@v4 |
| 29 | + with: |
| 30 | + node-version: 20 |
| 31 | + cache: npm |
| 32 | + cache-dependency-path: test/e2e/package-lock.json |
| 33 | + |
| 34 | + - name: Install Python dependencies |
| 35 | + working-directory: test/e2e |
| 36 | + run: pip install -r requirements.txt |
| 37 | + |
| 38 | + - name: Install npm dependencies |
| 39 | + working-directory: test/e2e |
| 40 | + run: npm ci |
| 41 | + |
| 42 | + - uses: extractions/setup-just@v2 |
| 43 | + |
| 44 | + - name: Build publisher binary |
| 45 | + run: just build |
| 46 | + |
| 47 | + - name: Download VSIX artifact |
| 48 | + uses: actions/download-artifact@v4 |
| 49 | + with: |
| 50 | + name: dist |
| 51 | + path: dist |
| 52 | + |
| 53 | + - name: Write Connect license file |
| 54 | + if: env.CONNECT_LICENSE != '' |
| 55 | + run: | |
| 56 | + mkdir -p ./test/e2e/licenses |
| 57 | + echo "$CONNECT_LICENSE" > ./test/e2e/licenses/connect-license.lic |
| 58 | + env: |
| 59 | + CONNECT_LICENSE: ${{ secrets.CONNECT_LICENSE }} |
| 60 | + |
| 61 | + - name: Build Docker images |
| 62 | + working-directory: test/e2e |
| 63 | + run: | |
| 64 | + docker build --build-arg GH_DOWNLOAD_TOKEN=${{ secrets.GH_DOWNLOAD_TOKEN }} -f Dockerfile.base -t e2ebase --platform linux/amd64 . |
| 65 | + docker compose build connect-publisher-e2e code-server |
| 66 | + env: |
| 67 | + CONNECT_LICENSE: ${{ secrets.CONNECT_LICENSE }} |
| 68 | + |
| 69 | + - name: "[DEBUG] Print host user and group info" |
| 70 | + if: always() && (env.DEBUG_CYPRESS == 'true' || runner.debug) |
| 71 | + run: | |
| 72 | + echo "USER: $(whoami)" |
| 73 | + echo "UID: $(id -u)" |
| 74 | + echo "GID: $(id -g)" |
| 75 | + id |
| 76 | + ls -ld . |
| 77 | +
|
| 78 | + - name: Ensure content-workspace exists |
| 79 | + run: mkdir -p ./test/e2e/content-workspace |
| 80 | + |
| 81 | + - name: Start containers |
| 82 | + working-directory: test/e2e |
| 83 | + run: | |
| 84 | + just start "connect-publisher-e2e" |
| 85 | + just start "code-server" |
| 86 | +
|
| 87 | + - name: Wait for Connect server to be ready |
| 88 | + run: | |
| 89 | + echo "Waiting for Connect server to be ready..." |
| 90 | + for i in {1..30}; do |
| 91 | + if curl -sf http://localhost:3939/__ping__ > /dev/null; then |
| 92 | + echo "Connect server is ready!" |
| 93 | + break |
| 94 | + fi |
| 95 | + if [ $i -eq 30 ]; then |
| 96 | + echo "WARNING: Timeout waiting for Connect server to be ready" |
| 97 | + # Continue anyway, don't exit with error |
| 98 | + fi |
| 99 | + echo "Waiting for Connect server (attempt $i/30)..." |
| 100 | + sleep 2 |
| 101 | + done |
| 102 | +
|
| 103 | + - name: Stop any processes using port 8080 |
| 104 | + run: | |
| 105 | + PID=$(docker exec publisher-e2e.code-server sh -c "lsof -ti :8080 || netstat -tulnp 2>/dev/null | grep ':8080' | awk '{print \$7}' | cut -d'/' -f1 | head -n1" || true) |
| 106 | + if [ -n "$PID" ]; then |
| 107 | + echo "Killing process $PID using port 8080" |
| 108 | + docker exec publisher-e2e.code-server kill -9 $PID || true |
| 109 | + else |
| 110 | + echo "No process using port 8080" |
| 111 | + fi |
| 112 | +
|
| 113 | + - name: Wait for code-server to be ready |
| 114 | + run: | |
| 115 | + for i in {1..30}; do |
| 116 | + if curl -sf http://localhost:8080 > /dev/null; then |
| 117 | + echo "code-server is ready!" |
| 118 | + break |
| 119 | + fi |
| 120 | + if [ $i -eq 30 ]; then |
| 121 | + echo "code-server did not become ready in time" |
| 122 | + exit 1 |
| 123 | + fi |
| 124 | + sleep 2 |
| 125 | + done |
| 126 | +
|
| 127 | + - name: Install and wait for VS Code extension to be ready |
| 128 | + working-directory: test/e2e |
| 129 | + run: | |
| 130 | + # Install the Publisher extension |
| 131 | + VSIX_FILENAME=$(ls -Art ../../dist | grep linux-amd64 | tail -n 1) |
| 132 | + docker compose exec code-server code-server --install-extension "/home/coder/vsix/${VSIX_FILENAME}" |
| 133 | +
|
| 134 | + # Wait for the extension to be installed (more reliable than process grep) |
| 135 | + echo "Waiting for Publisher extension to be installed..." |
| 136 | + for i in {1..60}; do |
| 137 | + if docker compose exec code-server code-server --list-extensions | grep -q posit.publisher; then |
| 138 | + echo "Publisher extension appears to be installed!" |
| 139 | + break |
| 140 | + fi |
| 141 | + if [ $i -eq 60 ]; then |
| 142 | + echo "WARNING: Timeout waiting for Publisher extension to be installed" |
| 143 | + fi |
| 144 | + echo "Waiting for extension installation (attempt $i/60)..." |
| 145 | + sleep 2 |
| 146 | + done |
| 147 | +
|
| 148 | + - name: "[DEBUG] Print workspace file ownership before tests" |
| 149 | + if: always() && (env.DEBUG_CYPRESS == 'true' || runner.debug) |
| 150 | + run: | |
| 151 | + docker exec publisher-e2e.code-server ls -lR /home/coder/workspace || true |
| 152 | +
|
| 153 | + - name: Prepare test environment |
| 154 | + working-directory: test/e2e |
| 155 | + run: | |
| 156 | + # Clean up any static TOML files before tests |
| 157 | + docker exec publisher-e2e.code-server rm -f /home/coder/workspace/static*.toml || true |
| 158 | +
|
| 159 | + - name: Run Cypress tests |
| 160 | + working-directory: test/e2e |
| 161 | + run: npx cypress run |
| 162 | + env: |
| 163 | + CONNECT_LICENSE: ${{ secrets.CONNECT_LICENSE }} |
| 164 | + CONNECT_CLOUD_ENV: ${{ env.CONNECT_CLOUD_ENV }} # defaults to staging |
| 165 | + CI: true |
| 166 | + DEBUG_CYPRESS: ${{ env.DEBUG_CYPRESS }} |
| 167 | + ACTIONS_STEP_DEBUG: ${{ runner.debug && 'true' || 'false' }} |
| 168 | + |
| 169 | + - name: Upload Cypress screenshots on failure |
| 170 | + uses: actions/upload-artifact@v4 |
| 171 | + if: failure() |
| 172 | + with: |
| 173 | + name: cypress-screenshots |
| 174 | + path: test/e2e/cypress/screenshots |
| 175 | + if-no-files-found: ignore |
| 176 | + |
| 177 | + - name: "[DEBUG] Upload Cypress videos" |
| 178 | + uses: actions/upload-artifact@v4 |
| 179 | + if: always() && (env.DEBUG_CYPRESS == 'true' || runner.debug) |
| 180 | + with: |
| 181 | + name: cypress-videos |
| 182 | + path: test/e2e/cypress/videos |
| 183 | + if-no-files-found: ignore |
| 184 | + |
| 185 | + - name: "[DEBUG] List installed VS Code extensions" |
| 186 | + if: always() && (env.DEBUG_CYPRESS == 'true' || runner.debug) |
| 187 | + working-directory: test/e2e |
| 188 | + run: docker compose exec code-server code-server --list-extensions --show-versions || true |
| 189 | + |
| 190 | + - name: "[DEBUG] Display test/e2e directory tree" |
| 191 | + if: always() && (env.DEBUG_CYPRESS == 'true' || runner.debug) |
| 192 | + run: | |
| 193 | + echo "Directory tree for test/e2e:" |
| 194 | + if command -v tree > /dev/null; then |
| 195 | + tree -I "node_modules|.venv" test/e2e |
| 196 | + else |
| 197 | + find test/e2e -path "*/node_modules" -prune -o -path "*/.venv" -prune -o -print |
| 198 | + fi |
| 199 | +
|
| 200 | + - name: "[DEBUG] Print code-server logs" |
| 201 | + if: always() && (env.DEBUG_CYPRESS == 'true' || runner.debug) |
| 202 | + run: | |
| 203 | + echo "=== Code Server Log Files ===" |
| 204 | + docker exec publisher-e2e.code-server bash -c 'find /root/.local -type f -name "*.log" 2>/dev/null | while read -r file; do echo "FILE: $file"; done' |
| 205 | + echo "=== Main Code Server Logs (sample) ===" |
| 206 | + docker exec publisher-e2e.code-server bash -c 'cat /root/.local/share/code-server/coder-logs/code-server-*.log 2>/dev/null | tail -n 50 || echo "No logs found"' |
| 207 | + echo "=== Extension Host Logs (sample) ===" |
| 208 | + docker exec publisher-e2e.code-server bash -c 'find /root/.local -name "remoteexthost.log" -type f 2>/dev/null | head -n 1 | xargs cat | tail -n 50 || echo "No logs found"' |
| 209 | +
|
| 210 | + - name: "[DEBUG] Print Publisher extension logs" |
| 211 | + if: always() && (env.DEBUG_CYPRESS == 'true' || runner.debug) |
| 212 | + run: | |
| 213 | + echo "=== Finding Publisher logs ===" |
| 214 | + docker exec publisher-e2e.code-server bash -c 'find /root -type f -name "*.log" 2>/dev/null | grep -i "posit\|publisher" || echo "No publisher logs found"' |
| 215 | + docker exec publisher-e2e.code-server bash -c 'find /home -type f -name "*.log" 2>/dev/null | grep -i "posit\|publisher" || echo "No publisher logs found"' |
| 216 | + echo "=======================================" |
| 217 | +
|
| 218 | + - name: "[DEBUG] Print code-server container logs" |
| 219 | + if: always() && (env.DEBUG_CYPRESS == 'true' || runner.debug) |
| 220 | + working-directory: test/e2e |
| 221 | + run: docker compose logs code-server || true |
| 222 | + |
| 223 | + - name: "[DEBUG] List /home/coder directory contents" |
| 224 | + if: always() && (env.DEBUG_CYPRESS == 'true' || runner.debug) |
| 225 | + run: docker exec publisher-e2e.code-server ls -lR /home/coder || true |
| 226 | + |
| 227 | + - name: Stop containers |
| 228 | + if: always() |
| 229 | + working-directory: test/e2e |
| 230 | + run: just stop |
0 commit comments