Last active
June 18, 2023 06:00
-
-
Save antischematic/565f1357c322bcfb09724b8e86eca595 to your computer and use it in GitHub Desktop.
Polly setup with leftest-cypress
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { defineConfig } from "cypress" | |
import { setupPollyTasks } from "./src/support/polly-tasks" | |
export default defineConfig({ | |
e2e: { | |
supportFile: "**/support/e2e.{js,jsx,ts,tsx}", | |
specPattern: "**/app.cy.ts", | |
testIsolation: false, | |
setupNodeEvents(on) { | |
setupPollyTasks(on) | |
} | |
}, | |
env: { | |
LEFTEST_TAGS: process.env.TAGS, | |
}, | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Persister, { Har } from "@pollyjs/persister" | |
export default class CypressPersister extends Persister { | |
store: { [key: string]: Har } | |
constructor() { | |
// @ts-ignore | |
super(...arguments) | |
this.store = { ...(<any>this.options).data } | |
} | |
static get id() { | |
return 'cypress-persister'; | |
} | |
async onFindRecording(recordingId) { | |
return this.store[recordingId] || null; | |
} | |
async onSaveRecording(recordingId, data) { | |
this.store[recordingId] = data; | |
} | |
async onDeleteRecording(recordingId) { | |
delete this.store[recordingId]; | |
} | |
toJSON() { | |
return this.store | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Har } from "@pollyjs/persister" | |
import * as Cypress from "cypress" | |
import * as fs from "fs" | |
import * as path from "path" | |
interface RecordEvent { | |
folder: string | |
spec: string | |
path: string | |
data: { [key: string]: Har }[] | |
fixtures: string[] | |
clean: boolean | |
} | |
function getFileName(folder: string, spec: string) { | |
return path.join(folder, `${spec}.json`) | |
} | |
function ensureFolderExists(dir: string) { | |
if (!fs.existsSync(dir)){ | |
fs.mkdirSync(dir, { recursive: true }); | |
} | |
} | |
export function setupPollyTasks(on: Cypress.PluginEvents) { | |
return on('task', { | |
loadData({ folder, spec }: RecordEvent) { | |
const filename = getFileName(folder, spec) | |
if (fs.existsSync(filename)) { | |
return JSON.parse(fs.readFileSync(filename, 'utf-8')) | |
} | |
return null | |
}, | |
saveData({ folder, spec, data, fixtures, clean }: RecordEvent) { | |
const existing = new Set(fixtures) | |
const filename = getFileName(folder, spec) | |
const recordings = Object.assign({}, ...data) as { [key: string]: any } | |
ensureFolderExists(folder) | |
if (clean) { | |
for (const [key, recording] of Object.entries(recordings)) { | |
if (!existing.has(recording.log._recordingName)) { | |
delete recordings[key] | |
} | |
} | |
} | |
fs.writeFileSync(filename, JSON.stringify(recordings, null, 2), 'utf-8') | |
return null | |
}, | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
afterScenario, | |
and, | |
beforeScenario, | |
eq, | |
getAllScenarios, | |
getTags, | |
not, only, | |
or, | |
ReadonlyScenario, | |
} from "@antischematic/leftest" | |
import FetchAdapter from "@pollyjs/adapter-fetch" | |
import XHRAdapter from "@pollyjs/adapter-xhr" | |
import { MODE, Polly, Timing } from "@pollyjs/core" | |
import { isTagUsed } from "../../../core/src/lib/core" | |
import CypressPersister from "./cypress-persister" | |
export const { record, replay } = getTags() | |
Polly.register(FetchAdapter) | |
Polly.register(XHRAdapter) | |
Polly.register(CypressPersister) | |
export interface RecorderOptions { | |
fixture: string | |
mode: MODE | |
data?: any | |
recordIfMissing?: boolean | |
} | |
let data: { [key: string]: any } | |
const recordings = [] | |
function setupPolly({ fixture, mode, data, recordIfMissing = false }: RecorderOptions) { | |
const polly = new Polly(fixture, { | |
mode, | |
adapters: ["fetch", "xhr"], | |
persister: "cypress-persister", | |
persisterOptions: { | |
"cypress-persister": { | |
data, | |
}, | |
}, | |
recordFailedRequests: true, | |
timing: Timing.fixed(0), | |
flushRequestsOnStop: true, | |
recordIfMissing | |
}) | |
polly.server.any().on("response", (request, response, event) => { | |
Cypress.log({ | |
name: "polly", | |
type: "parent", | |
message: mode === 'record' | |
? `Recorded request for ${request.url}` | |
: `Replayed request for ${request.url}`, | |
consoleProps() { | |
return { | |
request, | |
response, | |
event, | |
} | |
}, | |
}) | |
}) | |
return polly | |
} | |
function getFixture(scenario: ReadonlyScenario) { | |
return scenario.path.join(" ") | |
} | |
function setupBeforeLoadListener(scenario: ReadonlyScenario) { | |
const polly = scenario.data.polly as Polly | |
const beforeLoad = (scenario.data.windowBeforeLoad = (window: Window) => { | |
polly.configure({ | |
adapterOptions: { | |
fetch: { context: window }, | |
xhr: { context: window }, | |
}, | |
}) | |
polly.connectTo("fetch") | |
polly.connectTo("xhr") | |
}) | |
Cypress.on("window:before:load", beforeLoad) | |
} | |
beforeScenario(eq(record), (scenario) => { | |
if (scenario.data.polly) return | |
const fixture = getFixture(scenario) | |
scenario.data.polly = setupPolly({ | |
fixture, | |
mode: "record" | |
}) | |
setupBeforeLoadListener(scenario) | |
}) | |
afterScenario(or(record, replay), (scenario) => { | |
const polly = scenario.data.polly as Polly | |
recordings.push(polly.persister) | |
Cypress.off( | |
"window:before:load", | |
scenario.data.windowBeforeLoad as VoidFunction, | |
) | |
cy.then(() => polly.stop()) | |
}) | |
beforeScenario(eq(replay), (scenario) => { | |
if (scenario.data.polly) return | |
const fixture = getFixture(scenario) | |
scenario.data.polly = setupPolly({ | |
fixture, | |
mode: "replay", | |
data, | |
}) | |
setupBeforeLoadListener(scenario) | |
}) | |
before(() => { | |
cy.task("loadData", { | |
folder: Cypress.config("fixturesFolder"), | |
spec: Cypress.spec.name, | |
}).then((results = {}) => { | |
data = results | |
}) | |
}) | |
after(() => { | |
const scenarios = getAllScenarios() | |
const fixtures = scenarios | |
.filter((scenario) => scenario.hasTag(record) || scenario.hasTag(replay)) | |
.map(getFixture) | |
cy.task("saveData", { | |
folder: Cypress.config("fixturesFolder"), | |
spec: Cypress.spec.name, | |
data: recordings, | |
fixtures, | |
clean: !isTagUsed(only) | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment