Skip to content

Commit

Permalink
feat: Introduce an API for Percy Specific CSS (#346)
Browse files Browse the repository at this point in the history
* WIP: Add `percyCSS` config option

* feat: Implement & test percy specific CSS

* Lint, add global CSS test, concat global & snapshot CSS

* Only set `.percy.yml` for integration tests

Ideally the unit tests wouldn't be impacted by a real `.percy.yml` file. But
this is easier than refactoring the commands test suite.

* Add to percy example config file

* Add config service test, update casing to be consistant

* Update yml config to use better `|` operator

* Peer review feedback
  • Loading branch information
Robdel12 committed Sep 17, 2019
1 parent d1e812b commit 6158f57
Show file tree
Hide file tree
Showing 13 changed files with 79 additions and 10 deletions.
8 changes: 8 additions & 0 deletions .ci.percy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 1
snapshot:
percy-css: |
.percy-only-css-global {
height: 300px;
width: 300px;
background-color: blue;
}
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
- run:
name: Client tests
command: $NYC yarn test-client --singleRun
- run:
name: Setup integration test .percy.yml
command: mv .ci.percy.yml .percy.yml
- run:
name: Integration tests
command: $NYC yarn test-integration
Expand Down
1 change: 1 addition & 0 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface Configuration {
export const DEFAULT_CONFIGURATION: Configuration = {
'version': 1.0,
'snapshot': {
'percy-css': '',
'widths': [1280, 375], // px
'min-height': 1024, // px
},
Expand Down
1 change: 1 addition & 0 deletions src/configuration/snapshot-configuration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface SnapshotConfiguration {
widths: number[],
'percy-css': string,
'min-height': number,
'enable-javascript'?: boolean
}
6 changes: 1 addition & 5 deletions src/percy-agent-client/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,7 @@ class DOM {
*/
private mutateOriginalDOM() {
function createUID($el: Element) {
const ID =
'_' +
Math.random()
.toString(36)
.substr(2, 9)
const ID = `_${Math.random().toString(36).substr(2, 9)}`

$el.setAttribute('data-percy-element-id', ID)
}
Expand Down
1 change: 1 addition & 0 deletions src/percy-agent-client/percy-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default class PercyAgent {
this.client.post(SNAPSHOT_PATH, {
name,
url: documentObject.URL,
percyCSS: options.percyCSS,
// enableJavascript is deprecated. Use enableJavaScript
enableJavaScript: options.enableJavaScript || options.enableJavascript,
widths: options.widths,
Expand Down
1 change: 1 addition & 0 deletions src/percy-agent-client/snapshot-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export interface SnapshotOptions {
minHeight?: number,
minimumHeight?: number, // deprecated. Use minHeight
document?: Document,
percyCSS?: string
}
14 changes: 13 additions & 1 deletion src/services/agent-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,25 @@ export class AgentService {
if (!this.snapshotService) { return response.json({success: false}) }

const configuration = new ConfigurationService().configuration
// trim the string of whitespace and concat per-snapshot CSS with the globally specified CSS
const percySpecificCSS = configuration.snapshot['percy-css'].concat(request.body.percyCSS || '').trim()
const snapshotOptions: SnapshotOptions = {
percyCSS: percySpecificCSS,
widths: request.body.widths || configuration.snapshot.widths,
enableJavaScript: request.body.enableJavaScript != null
? request.body.enableJavaScript
: configuration.snapshot['enable-javascript'],
minHeight: request.body.minHeight || configuration.snapshot['min-height'],
}

const domSnapshot = request.body.domSnapshot
let domSnapshot = request.body.domSnapshot
const percyCSSFileName = `percy-specific.${Date.now()}.css` as string

// Inject the link to the percy specific css if the option is passed
if (snapshotOptions.percyCSS) {
const cssLink = `<link data-percy-specific-css rel="stylesheet" href="/${percyCSSFileName}" />`
domSnapshot = domSnapshot.replace(/<\/body>/i, cssLink + '$&')
}

if (domSnapshot.length > Constants.MAX_FILE_SIZE_BYTES) {
logger.info(`snapshot skipped[max_file_size_exceeded]: '${request.body.name}'`)
Expand All @@ -118,6 +128,8 @@ export class AgentService {

resources = resources.concat(
this.snapshotService.buildLogResource(snapshotLog),
// @ts-ignore we won't write anything if css is not is passed
this.snapshotService.buildPercyCSSResource(percyCSSFileName, snapshotOptions.percyCSS),
)

const snapshotCreation = this.snapshotService.create(
Expand Down
19 changes: 19 additions & 0 deletions src/services/snapshot-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,25 @@ export default class SnapshotService extends PercyClientService {
})
}

buildPercyCSSResource(fileName: string, css: string) {
if (!css) { return [] }
const buffer = Buffer.from(css, 'utf8')
const sha = crypto.createHash('sha256').update(buffer).digest('hex')
const localPath = path.join(os.tmpdir(), sha)

// write the SHA file if it doesn't exist
if (!fs.existsSync(localPath)) {
fs.writeFileSync(localPath, buffer, 'utf8')
}

return this.percyClient.makeResource({
resourceUrl: `/${fileName}`,
mimetype: 'text/css',
localPath,
sha,
})
}

create(
name: string,
resources: any[],
Expand Down
21 changes: 17 additions & 4 deletions test/integration/agent-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ declare var PercyAgent: any

async function snapshot(page: puppeteer.Page, name: string, options: any = {}) {
await page.addScriptTag({path: agentJsFilename()})

const domSnapshot = await page.evaluate((name: string, options: any) => {
const percyAgentClient = new PercyAgent({ handleAgentCommunication: false })

return percyAgentClient.snapshot(name, options)
}, name, options)

Expand Down Expand Up @@ -61,10 +63,10 @@ describe('Integration test', () => {
expect(domSnapshot).contains('Example Domain')
})

it('snapshots an HTTPS site', async () => {
await page.goto('https://example.com')
const domSnapshot = await snapshot(page, 'Example.com HTTPS snapshot')
expect(domSnapshot).contains('Example Domain')
it('snapshots an HTTPS, CORS, HSTS, & CSP site', async () => {
await page.goto('https://sdk-test.percy.dev')
const domSnapshot = await snapshot(page, 'SDK example page snapshot')
expect(domSnapshot).contains('SDK Test Website')
})

it('snapshots an invalid HTTPS site', async () => {
Expand Down Expand Up @@ -102,6 +104,17 @@ describe('Integration test', () => {
server.close()
})

it('applies Percy specific CSS', async () => {
await page.goto(`http://localhost:${PORT}/percy-specific-css.html`)
await snapshot(page, 'Percy Specific CSS', {
percyCSS: `.percy-only-css-snapshot {
height: 100px;
width: 100px;
background-color: purple;
}`,
})
})

describe('large resources', () => {
it('snapshots large DOM', async () => {
await page.goto(`http://localhost:${PORT}/exceeds-dom-snapshot-size-limit.html`)
Expand Down
9 changes: 9 additions & 0 deletions test/integration/testcases/percy-specific-css.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<head>
<title>Percy Specific CSS testing</title>
</head>
<body>
<div class="percy-only-css-snapshot"></div>
<div class="percy-only-css-global"></div>
</body>
</html>
1 change: 1 addition & 0 deletions test/services/configuration-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('ConfigurationService', () => {
expect(subject.snapshot.widths).to.eql([375, 1280])
expect(subject.snapshot['min-height']).to.eql(1024)
expect(subject.snapshot['enable-javascript']).to.eql(true)
expect(subject.snapshot['percy-css']).to.eql('iframe {\n display: none;\n}\n')
expect(subject['static-snapshots'].path).to.eql('_site/')
expect(subject['static-snapshots'].port).to.eql(9999)
expect(subject['static-snapshots']['base-url']).to.eql('/blog/')
Expand Down
4 changes: 4 additions & 0 deletions test/support/.percy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ snapshot:
widths: [375, 1280]
min-height: 1024 # px
enable-javascript: true
percy-css: |
iframe {
display: none;
}
static-snapshots:
path: _site/
port: 9999
Expand Down

0 comments on commit 6158f57

Please sign in to comment.