-
Notifications
You must be signed in to change notification settings - Fork 1.3k
316 lines (283 loc) · 13.9 KB
/
ci-e2e.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#
# This workflow runs CI E2E tests with Cypress.
#
# It relies on the container image built by 'container-images-ci.yml'.
#
name: E2E CI
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
timeout-minutes: 5
name: Determine need to run E2E checks
# Set job outputs to values from filter step
outputs:
shouldTriggerCypress: ${{ steps.changes.outputs.shouldTriggerCypress }}
steps:
# For pull requests it's not necessary to check out the code
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
shouldTriggerCypress:
# Avoid running E2E tests for irrelevant changes
# NOTE: we are at risk of missing a dependency here. We could make
# the dependencies more clear if we separated the backend/frontend
# code completely
- 'ee/**'
- 'posthog/**'
- 'bin/*'
- frontend/**/*
- requirements.txt
- requirements-dev.txt
- package.json
- pnpm-lock.yaml
# Make sure we run if someone is explicitly change the workflow
- .github/workflows/ci-e2e.yml
- .github/actions/build-n-cache-image/action.yml
# We use docker compose for tests, make sure we rerun on
# changes to docker-compose.dev.yml e.g. dependency
# version changes
- docker-compose.dev.yml
- Dockerfile
- cypress/**
# Job that lists and chunks spec file names and caches node modules
chunks:
needs: changes
name: Cypress preparation
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
chunks: ${{ steps.chunk.outputs.chunks }}
steps:
- name: Check out
uses: actions/checkout@v3
- name: Group spec files into chunks of three
id: chunk
run: echo "chunks=$(ls cypress/e2e/* | jq --slurp --raw-input -c 'split("\n")[:-1] | _nwise(2) | join("\n")' | jq --slurp -c .)" >> $GITHUB_OUTPUT
container:
name: Build and cache container image
runs-on: ubuntu-latest
timeout-minutes: 60
needs: [changes]
permissions:
contents: read
id-token: write # allow issuing OIDC tokens for this workflow run
outputs:
tag: ${{ steps.build.outputs.tag }}
build-id: ${{ steps.build.outputs.build-id }}
steps:
- name: Checkout
if: needs.changes.outputs.shouldTriggerCypress == 'true'
uses: actions/checkout@v3
- name: Build the Docker image with Depot
if: needs.changes.outputs.shouldTriggerCypress == 'true'
# Build the container image in preparation for the E2E tests
uses: ./.github/actions/build-n-cache-image
id: build
with:
save: true
actions-id-token-request-url: ${{ env.ACTIONS_ID_TOKEN_REQUEST_URL }}
cypress:
name: Cypress E2E tests (${{ strategy.job-index }})
runs-on: ubuntu-latest
timeout-minutes: 60
needs: [chunks, changes, container]
permissions:
id-token: write # allow issuing OIDC tokens for this workflow run
strategy:
# when one test fails, DO NOT cancel the other
# containers, as there may be other spec failures
# we want to know about.
fail-fast: false
matrix:
chunk: ${{ fromJson(needs.chunks.outputs.chunks) }}
steps:
- name: Checkout
if: needs.changes.outputs.shouldTriggerCypress == 'true'
uses: actions/checkout@v3
- name: Install pnpm
if: needs.changes.outputs.shouldTriggerCypress == 'true'
uses: pnpm/action-setup@v4
- name: Set up Node.js
if: needs.changes.outputs.shouldTriggerCypress == 'true'
uses: actions/setup-node@v4
with:
node-version: 18.12.1
- name: Get pnpm cache directory path
if: needs.changes.outputs.shouldTriggerCypress == 'true'
id: pnpm-cache-dir
run: echo "PNPM_STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Get cypress cache directory path
if: needs.changes.outputs.shouldTriggerCypress == 'true'
id: cypress-cache-dir
run: echo "CYPRESS_BIN_PATH=$(npx cypress cache path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v4
if: needs.changes.outputs.shouldTriggerCypress == 'true'
id: pnpm-cache
with:
path: |
${{ steps.pnpm-cache-dir.outputs.PNPM_STORE_PATH }}
${{ steps.cypress-cache-dir.outputs.CYPRESS_BIN_PATH }}
key: ${{ runner.os }}-pnpm-cypress-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-cypress-
- name: Install package.json dependencies with pnpm
if: needs.changes.outputs.shouldTriggerCypress == 'true'
run: pnpm install --frozen-lockfile
- name: Stop/Start stack with Docker Compose
# these are required checks so, we can't skip entire sections
if: needs.changes.outputs.shouldTriggerCypress == 'true'
run: |
docker compose -f docker-compose.dev.yml down
docker compose -f docker-compose.dev.yml up -d
- name: Wait for ClickHouse
# these are required checks so, we can't skip entire sections
if: needs.changes.outputs.shouldTriggerCypress == 'true'
run: ./bin/check_kafka_clickhouse_up
- name: Install Depot CLI
if: needs.changes.outputs.shouldTriggerCypress == 'true'
uses: depot/setup-action@v1
- name: Get Docker image cached in Depot
if: needs.changes.outputs.shouldTriggerCypress == 'true'
uses: depot/pull-action@v1
with:
build-id: ${{ needs.container.outputs.build-id }}
tags: ${{ needs.container.outputs.tag }}
- name: Write .env # This step intentionally has no if, so that GH always considers the action as having run
run: |
cat <<EOT >> .env
SECRET_KEY=6b01eee4f945ca25045b5aab440b953461faf08693a9abbf1166dc7c6b9772da
REDIS_URL=redis://localhost
DATABASE_URL=postgres://posthog:posthog@localhost:5432/posthog
KAFKA_HOSTS=kafka:9092
DISABLE_SECURE_SSL_REDIRECT=1
SECURE_COOKIES=0
OPT_OUT_CAPTURE=0
E2E_TESTING=1
SKIP_SERVICE_VERSION_REQUIREMENTS=1
EMAIL_HOST=email.test.posthog.net
SITE_URL=http://localhost:8000
NO_RESTART_LOOP=1
CLICKHOUSE_SECURE=0
OBJECT_STORAGE_ENABLED=1
OBJECT_STORAGE_ENDPOINT=http://localhost:19000
OBJECT_STORAGE_ACCESS_KEY_ID=object_storage_root_user
OBJECT_STORAGE_SECRET_ACCESS_KEY=object_storage_root_password
GITHUB_ACTION_RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
CELERY_METRICS_PORT=8999
CLOUD_DEPLOYMENT=E2E
ENCRYPTION_SALT_KEYS=00beef0000beef0000beef0000beef00
EOT
- name: Start PostHog
# these are required checks so, we can't skip entire sections
if: needs.changes.outputs.shouldTriggerCypress == 'true'
run: |
mkdir -p /tmp/logs
echo "Starting PostHog using the container image ${{ needs.container.outputs.tag }}"
DOCKER_RUN="docker run --rm --network host --add-host kafka:127.0.0.1 --env-file .env ${{ needs.container.outputs.tag }}"
$DOCKER_RUN ./bin/migrate
$DOCKER_RUN python manage.py setup_dev
# only starts the plugin server so that the "wait for PostHog" step passes
$DOCKER_RUN ./bin/docker-worker &> /tmp/logs/worker.txt &
$DOCKER_RUN ./bin/docker-server &> /tmp/logs/server.txt &
- name: Wait for PostHog
# these are required checks so, we can't skip entire sections
if: needs.changes.outputs.shouldTriggerCypress == 'true'
# this action might be abandoned - but v1 doesn't point to latest of v1 (which it should)
# so pointing to v1.1.0 to remove warnings about node version with v1
# todo check https://github.com/iFaxity/wait-on-action/releases for new releases
uses: iFaxity/[email protected]
timeout-minutes: 3
with:
verbose: true
log: true
resource: http://localhost:8000
- name: Cypress run
# these are required checks so, we can't skip entire sections
if: needs.changes.outputs.shouldTriggerCypress == 'true'
uses: cypress-io/github-action@v6
with:
config-file: cypress.e2e.config.ts
config: retries=2
spec: ${{ matrix.chunk }}
install: false
# We were seeing suprising crashes in headless mode
# See https://github.com/cypress-io/cypress/issues/28893#issuecomment-1956480875
headed: true
env:
E2E_TESTING: 1
OPT_OUT_CAPTURE: 0
GITHUB_ACTION_RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
- name: Archive test screenshots
uses: actions/upload-artifact@v3
with:
name: screenshots
path: cypress/screenshots
if: ${{ failure() }}
- name: Archive test downloads
uses: actions/upload-artifact@v3
with:
name: downloads
path: cypress/downloads
if: ${{ failure() }}
- name: Archive test videos
uses: actions/upload-artifact@v3
with:
name: videos
path: cypress/videos
if: ${{ failure() }}
- name: Archive accessibility violations
if: needs.changes.outputs.shouldTriggerCypress == 'true'
uses: actions/upload-artifact@v3
with:
name: accessibility-violations
path: '**/a11y/'
if-no-files-found: 'ignore'
- name: Show logs on failure
# use artefact here, as I think the output will be too large for display in an action
uses: actions/upload-artifact@v3
with:
name: logs-${{ strategy.job-index }}
path: /tmp/logs
if: ${{ failure() }}
calculate-running-time:
name: Calculate running time
runs-on: ubuntu-latest
needs: [cypress]
if: needs.changes.outputs.shouldTriggerCypress == 'true' &&
github.event.pull_request.head.repo.full_name == 'PostHog/posthog'
steps:
- name: Calculate running time
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token
run_id=${GITHUB_RUN_ID}
repo=${GITHUB_REPOSITORY}
run_info=$(gh api repos/${repo}/actions/runs/${run_id})
echo run_info: ${run_info}
# name is the name of the workflow file
# run_started_at is the start time of the workflow
# we want to get the number of seconds between the start time and now
name=$(echo ${run_info} | jq -r '.name')
run_url=$(echo ${run_info} | jq -r '.url')
run_started_at=$(echo ${run_info} | jq -r '.run_started_at')
run_attempt=$(echo ${run_info} | jq -r '.run_attempt')
start_seconds=$(date -d "${run_started_at}" +%s)
now_seconds=$(date +%s)
duration=$((now_seconds-start_seconds))
echo running_time_duration_seconds=${duration} >> $GITHUB_ENV
echo running_time_run_url=${run_url} >> $GITHUB_ENV
echo running_time_run_attempt=${run_attempt} >> $GITHUB_ENV
echo running_time_run_id=${run_id} >> $GITHUB_ENV
echo running_time_run_started_at=${run_started_at} >> $GITHUB_ENV
- name: Capture running time to PostHog
if: github.event.pull_request.head.repo.full_name == 'PostHog/posthog'
uses: PostHog/[email protected]
with:
posthog-token: ${{secrets.POSTHOG_API_TOKEN}}
event: 'posthog-ci-running-time'
properties: '{"duration_seconds": ${{ env.running_time_duration_seconds }}, "run_url": "${{ env.running_time_run_url }}", "run_attempt": "${{ env.running_time_run_attempt }}", "run_id": "${{ env.running_time_run_id }}", "run_started_at": "${{ env.running_time_run_started_at }}"}'