Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
106 changes: 53 additions & 53 deletions docs/api/commands/prompt.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -347,43 +347,43 @@ Placeholders let you use dynamic and sensitive values while maintaining cache ef
```typescript
describe('Campaign Management', () => {
it('creates multiple campaigns with different discount percentages', () => {
const adminPassword = Cypress.env('ADMIN_PASSWORD')

const campaigns = [
{ name: 'Fall Sale', discountPct: 10 },
{ name: 'Spring Sale', discountPct: 15 },
]

// This loop demonstrates the caching benefit:
// - First iteration: AI generates and caches the code
// - Subsequent iterations: Reuse cached code with different placeholder values
campaigns.forEach((campaign) => {
const campaignName = campaign.name
const campaignDiscountPct = campaign.discountPct

cy.prompt(
[
`Visit ${Cypress.env('ADMIN_URL')}/admin/login`,
// Using {{adminPassword}} prevents this sensitive value from being sent to AI
'Type {{adminPassword}} into the password field',
'Click Sign In',
'Open the Promotions tab',
'Click to create a new campaign',
// Using {{campaignName}} and {{campaignDiscountPct}}
// allows for high performance caching with different values
'Type {{campaignName}} into the name field',
'Set discount to {{campaignDiscountPct}}% for all products',
'Save the campaign',
'Verify the campaign "{{campaignName}}" appears in the list',
],
{
placeholders: {
adminPassword,
campaignName,
campaignDiscountPct,
},
}
)
cy.task('getSecret', 'ADMIN_PASSWORD').then((adminPassword) => {
const campaigns = [
{ name: 'Fall Sale', discountPct: 10 },
{ name: 'Spring Sale', discountPct: 15 },
]

// This loop demonstrates the caching benefit:
// - First iteration: AI generates and caches the code
// - Subsequent iterations: Reuse cached code with different placeholder values
campaigns.forEach((campaign) => {
const campaignName = campaign.name
const campaignDiscountPct = campaign.discountPct

cy.prompt(
[
`Visit ${Cypress.env('ADMIN_URL')}/admin/login`,
// Using {{adminPassword}} prevents this sensitive value from being sent to AI
'Type {{adminPassword}} into the password field',
'Click Sign In',
'Open the Promotions tab',
'Click to create a new campaign',
// Using {{campaignName}} and {{campaignDiscountPct}}
// allows for high performance caching with different values
'Type {{campaignName}} into the name field',
'Set discount to {{campaignDiscountPct}}% for all products',
'Save the campaign',
'Verify the campaign "{{campaignName}}" appears in the list',
],
{
placeholders: {
adminPassword,
campaignName,
campaignDiscountPct,
},
}
)
})
})
})
})
Expand Down Expand Up @@ -567,7 +567,7 @@ describe('Feature: User Registration', () => {
],
{
placeholders: {
password: Cypress.env('PASSWORD'),
password: 'PASSWORD_PLACEHOLDER', // Use cy.task('getSecret', 'PASSWORD') to get the actual value
},
}
)
Expand Down Expand Up @@ -687,20 +687,20 @@ The commands in the generated code may look like:
### Login flow

```javascript
const password = Cypress.env('adminPassword')

cy.prompt(
[
'visit the login page',
'type "[email protected]" in the email field',
'type {{password}} in the password field',
'click the login button',
'verify we are redirected to the dashboard',
],
{
placeholders: { password },
}
)
cy.task('getSecret', 'ADMIN_PASSWORD').then((password) => {
cy.prompt(
[
'visit the login page',
'type "[email protected]" in the email field',
'type {{password}} in the password field',
'click the login button',
'verify we are redirected to the dashboard',
],
{
placeholders: { password },
}
)
})
```

### E-commerce checkout
Expand Down Expand Up @@ -794,7 +794,7 @@ cy.origin('https://cloud.cypress.io/login', () => {
],
{
placeholders: {
password: Cypress.env('PASSWORD'),
password: 'PASSWORD_PLACEHOLDER',
},
}
)
Expand Down
37 changes: 37 additions & 0 deletions docs/api/commands/task.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,43 @@ on('task', {

:::

### Accessing secrets securely

You can use `cy.task()` to securely access secrets from the Node.js process environment variables. This is the recommended approach for accessing sensitive values like passwords, API keys, or tokens, as they remain in the Node.js process and are not exposed to the browser.

:::cypress-config-plugin-example

```ts
on('task', {
getSecret(secretName: string) {
const secret = process.env[secretName]

if (!secret) {
throw new Error(`Secret ${secretName} is not set`)
}

return secret
},
})
```

:::

```javascript
// in test
cy.task('getSecret', 'API_KEY').then((apiKey) => {
cy.request({
method: 'POST',
url: 'https://api.example.com/data',
headers: {
Authorization: `Bearer ${apiKey}`,
},
})
})
```

In CI environments, you can set these secrets as environment variables in your CI provider's secret management system. For example:

## Rules

<HeaderRequirements />
Expand Down
19 changes: 9 additions & 10 deletions docs/api/commands/wrap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,15 @@ import { userService } from '../../src/_services/user.service'
it('can assert against resolved object using .should', () => {
cy.log('user service login')
const username = Cypress.env('username')
const password = Cypress.env('password')

// wrap the promise returned by the application code
cy.wrap(userService.login(username, password))
// check the yielded object
.should('be.an', 'object')
.and('have.keys', ['firstName', 'lastName', 'username', 'id', 'token'])
.and('contain', {
username: 'test',
firstName: 'Test',
cy.task('getSecret', 'PASSWORD').then((password) => {
// wrap the promise returned by the application code
cy.wrap(userService.login(username, password))
// check the yielded object
.should('be.an', 'object')
.and('have.keys', ['firstName', 'lastName', 'username', 'id', 'token'])
.and('contain', {
username: 'test',
firstName: 'Test',
lastName: 'User',
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,21 @@ const awsConfig = require('./aws-exports-es5.js')
```js
{
env: {
cognito_username: process.env.AWS_COGNITO_USERNAME,
cognito_password: process.env.AWS_COGNITO_PASSWORD,
awsConfig: awsConfig.default
}
},
setupNodeEvents(on, config) {
on('task', {
getAuthCredentials() {
const username = process.env.AWS_COGNITO_USERNAME
const password = process.env.AWS_COGNITO_PASSWORD
if (!username || !password) {
throw new Error('AWS_COGNITO_USERNAME and AWS_COGNITO_PASSWORD must be set')
}
return { username, password }
},
})
return config
},
}
```

Expand Down Expand Up @@ -253,10 +264,9 @@ describe('Cognito, cy.origin() login', function () {
cy.task('db:seed')

// login via Amazon Cognito via cy.origin()
cy.loginByCognito(
Cypress.env('cognito_username'),
Cypress.env('cognito_password')
)
cy.task('getAuthCredentials').then(({ username, password }) => {
cy.loginByCognito(username, password)
})
})

it('shows onboarding', function () {
Expand Down Expand Up @@ -454,10 +464,9 @@ describe('Cognito, programmatic login', function () {
cy.task('db:seed')

// Programmatically login via Amazon Cognito API
cy.loginByCognitoApi(
Cypress.env('cognito_username'),
Cypress.env('cognito_password')
)
cy.task('getAuthCredentials').then(({ username, password }) => {
cy.loginByCognitoApi(username, password)
})
})

it('shows onboarding', function () {
Expand Down
85 changes: 52 additions & 33 deletions docs/app/guides/authentication-testing/auth0-authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,30 @@ require('dotenv').config()
```js
{
env: {
auth0_username: process.env.AUTH0_USERNAME,
auth0_password: process.env.AUTH0_PASSWORD,
auth0_domain: process.env.REACT_APP_AUTH0_DOMAIN,
auth0_audience: process.env.REACT_APP_AUTH0_AUDIENCE,
auth0_scope: process.env.REACT_APP_AUTH0_SCOPE,
auth0_client_id: process.env.REACT_APP_AUTH0_CLIENTID,
auth0_client_secret: process.env.AUTH0_CLIENT_SECRET,
},
setupNodeEvents(on, config) {
on('task', {
getAuthCredentials() {
const username = process.env.AUTH0_USERNAME
const password = process.env.AUTH0_PASSWORD
if (!username || !password) {
throw new Error('AUTH0_USERNAME and AUTH0_PASSWORD must be set')
}
return { username, password }
},
getSecret(secretName) {
const secret = process.env[secretName]
if (!secret) {
throw new Error(`Secret ${secretName} is not set`)
}
return secret
},
})
return config
},
}
```
Expand Down Expand Up @@ -177,10 +194,10 @@ describe('Auth0', function () {
beforeEach(function () {
cy.task('db:seed')
cy.intercept('POST', '/graphql').as('createBankAccount')
cy.loginToAuth0(
Cypress.env('auth0_username'),
Cypress.env('auth0_password')
)

cy.task('getAuthCredentials').then(({ username, password }) => {
cy.loginToAuth0(username, password)
})
cy.visit('/')
})

Expand Down Expand Up @@ -256,23 +273,23 @@ Cypress.Commands.add(
(username: string, password: string) => {
cy.log(`Logging in as ${username}`)
const client_id = Cypress.env('auth0_client_id')
const client_secret = Cypress.env('auth0_client_secret')
const audience = Cypress.env('auth0_audience')
const scope = Cypress.env('auth0_scope')

cy.request({
method: 'POST',
url: `https://${Cypress.env('auth0_domain')}/oauth/token`,
body: {
grant_type: 'password',
username,
password,
audience,
scope,
client_id,
client_secret,
},
}).then(({ body }) => {
cy.task('getSecret', 'AUTH0_CLIENT_SECRET').then((client_secret) => {
cy.request({
method: 'POST',
url: `https://${Cypress.env('auth0_domain')}/oauth/token`,
body: {
grant_type: 'password',
username,
password,
audience,
scope,
client_id,
client_secret,
},
}).then(({ body }) => {
const claims = jwt.decode(body.id_token)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tabs appear off in this section.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was probably fixed in a lint check I've run since then.

const {
nickname,
Expand Down Expand Up @@ -309,6 +326,7 @@ Cypress.Commands.add(
window.localStorage.setItem('auth0Cypress', JSON.stringify(item))

cy.visit('/')
})
})
}
)
Expand All @@ -324,10 +342,9 @@ onboarding process and logout.
describe('Auth0', function () {
beforeEach(function () {
cy.task('db:seed')
cy.loginByAuth0Api(
Cypress.env('auth0_username'),
Cypress.env('auth0_password')
)
cy.task('getAuthCredentials').then(({ username, password }) => {
cy.loginByAuth0Api(username, password)
})
})

it('shows onboarding', function () {
Expand Down Expand Up @@ -641,14 +658,16 @@ Cypress.Commands.add('loginByAuth0Api', (username, password) => {
cy.exec('curl -4 icanhazip.com')
.its('stdout')
.then((ip) => {
cy.request({
method: 'DELETE',
url: `https://${Cypress.env(
'auth0_domain'
)}/api/v2/anomaly/blocks/ips/${ip}`,
auth: {
bearer: Cypress.env('auth0_mgmt_api_token'),
},
cy.task('getSecret', 'AUTH0_MGMT_API_TOKEN').then((token) => {
cy.request({
method: 'DELETE',
url: `https://${Cypress.env(
'auth0_domain'
)}/api/v2/anomaly/blocks/ips/${ip}`,
auth: {
bearer: token,
},
})
})
})

Expand Down
Loading