Use anchors as checklist items.
If CI can’t run it by env contract, it doesn’t count as “working.”
npx cypress run).API_BASE_URL is required; missing env fails fast.API_BASE_URL |
Base URL to the API host. Examples:
Rule
Do not bake |
API_EMAIL | RW user email for login/token acquisition (if POST requires auth). |
API_PASSWORD | RW user password for login/token acquisition. |
API_TOKEN | Optional. If supplied, tests may use it instead of logging in. Prefer token acquisition for repeatability unless tokens are stable by design. |
Never commit credentials or tokens. Use env vars locally and CI secret variables for pipelines.
npm install --save-dev cypress npx cypress open
This creates cypress/ folders and a baseline config. We keep this repo API-first in Sprint 09.
Tests validate structure + meaning. Avoid “status code only” tests.
cypress/e2e/api/articles.cy.jsnpm install --save-dev cypress npx cypress open // cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: process.env.API_BASE_URL,
setupNodeEvents(on, config) {
if (!config.baseUrl) {
throw new Error('Missing required env: API_BASE_URL')
}
return config
},
},
}) describe('API.ARTICLES.001 - Get Articles', () => {
it('returns list with expected shape', () => {
cy.request('GET', '/api/articles').then((res) => {
expect(res.status).to.eq(200)
expect(res.body).to.have.property('articles')
expect(res.body.articles).to.be.an('array')
if (res.body.articles.length > 0) {
const a = res.body.articles[0]
expect(a).to.have.property('title')
expect(a).to.have.property('slug')
expect(a).to.have.property('author')
}
})
})
})
describe('API.ARTICLES.002 - Create Article', () => {
it('creates a unique article (requires auth)', () => {
const title = `Sprint09 ${Date.now()}`
const token = Cypress.env('API_TOKEN')
// If your API requires login, set API_TOKEN via env or add a login helper in Sprint 10.
expect(token, 'API_TOKEN is required for create').to.be.a('string').and.not.be.empty
cy.request({
method: 'POST',
url: '/api/articles',
headers: { Authorization: `Token ${token}` },
body: {
article: {
title,
description: 'Cypress API harness seed',
body: 'Sprint 09 POST test'
}
}
}).then((res) => {
expect(res.status).to.eq(201)
expect(res.body).to.have.property('article')
expect(res.body.article.title).to.eq(title)
expect(res.body.article).to.have.property('slug')
})
})
})
describe('API.ARTICLES.003 - Unauthorized Create', () => {
it('fails with 401 when no token provided', () => {
cy.request({
method: 'POST',
url: '/api/articles',
failOnStatusCode: false,
body: {
article: {
title: `ShouldFail ${Date.now()}`,
description: 'no auth',
body: 'negative path'
}
}
}).then((res) => {
expect(res.status).to.eq(401)
})
})
}) export API_BASE_URL="http://sut.testlab:3001"
curl -sS "$API_BASE_URL/api/articles" | head export API_BASE_URL="http://sut.testlab:3001"
export API_TOKEN="<token>" # store in shell/CI secrets, never commit
npx cypress run
Sprint 09 allows using API_TOKEN directly.
Sprint 10 adds deterministic token acquisition (login) + request helpers.
export API_BASE_URL="http://sut.testlab:3001"
curl -sS "$API_BASE_URL/api/articles" | head export API_BASE_URL="http://sut.testlab:3001"
export API_TOKEN="<token>" # store in shell/CI secrets, never commit
npx cypress run
If you are in NAT/port-forward mode, base URL may be http://127.0.0.1:3001 instead.
API_BASE_URL fails immediately with a clear error./api but tests also call /api/... → double /api/api. Pick one convention.API_TOKEN or implement login helper (Sprint 10).sut.testlab (name/IP contract).This sprint proves the harness. Sprint 10 hardens auth + helpers. Sprint 11 turns it into a CI-grade signal.