Skip to content

Commit c83012b

Browse files
refactor(e2e): replace with cy-ai
https://www.npmjs.com/package/cy-ai
1 parent b4aa811 commit c83012b

File tree

6 files changed

+26
-131
lines changed

6 files changed

+26
-131
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![cypress](https://github.com/ai-action/cypress-ai-demo/actions/workflows/cypress.yml/badge.svg)](https://github.com/ai-action/cypress-ai-demo/actions/workflows/cypress.yml)
44

5-
[Cypress](https://www.cypress.io/) AI Demo.
5+
[Cypress AI](https://github.com/ai-action/cy-ai) demo.
66

77
## Prerequisites
88

@@ -35,13 +35,13 @@ npm install
3535

3636
## Run
3737

38-
Start Ollama:
38+
Start Ollama server:
3939

4040
```sh
4141
ollama serve
4242
```
4343

44-
Download [model](https://ollama.com/library/qwen2.5-coder):
44+
Download the [LLM](https://ollama.com/library/qwen2.5-coder):
4545

4646
```sh
4747
ollama pull qwen2.5-coder

cypress/e2e/__generated__/home.cy.ts.json renamed to cypress/e2e/__generated__/example.cy.ts.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
describe('template spec', () => {
2-
it('passes', () => {
1+
describe('example.cypress.io', () => {
2+
it('submits form', () => {
33
cy.ai('go to example.cypress.io and see heading "Kitchen Sink"');
44
cy.ai('click on "Commands" nav dropdown and click on link "Actions"');
55
cy.ai('find label "Coupon Code" and type "HALFOFF" in text field');

cypress/support/commands.ts

Lines changed: 1 addition & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -36,119 +36,4 @@
3636
// }
3737
// }
3838

39-
import { PromptTemplate } from '@langchain/core/prompts';
40-
import { Ollama } from '@langchain/ollama';
41-
import { sanitize } from 'dompurify';
42-
import { resolve } from 'path';
43-
44-
const llm = new Ollama({
45-
model: 'qwen2.5-coder',
46-
numCtx: 16384, // truncating input prompt limit=4096
47-
});
48-
49-
const template = `
50-
You're a QA engineer writing an E2E test step with Cypress
51-
52-
Rules:
53-
1. Return raw JavaScript code with "cy" commands without "describe" and "it"
54-
2. Write the minimum number of "cy" commands
55-
3. Don't perform an action or assertion unless the element is visible in the DOM
56-
4. Prefer locating with text or accessible label
57-
5. Ensure selectors are unique and specific enough to select 1 element
58-
59-
Task: {task}
60-
61-
DOM:
62-
\`\`\`html
63-
{html}
64-
\`\`\`
65-
`;
66-
67-
const prompt = PromptTemplate.fromTemplate(template.trim());
68-
const chain = prompt.pipe(llm);
69-
70-
function minutesToMilliseconds(minutes: number) {
71-
return 1000 * 60 * minutes;
72-
}
73-
74-
function getGeneratedFilePath(): string {
75-
return resolve(
76-
Cypress.spec.absolute,
77-
`../__generated__/${Cypress.spec.name}.json`,
78-
);
79-
}
80-
81-
const noop = () => {};
82-
83-
function readGeneratedFile() {
84-
return cy.readFile(getGeneratedFilePath(), { log: false }).should(noop);
85-
}
86-
87-
function getTestKey(): string {
88-
return Cypress.currentTest.titlePath.join(' ');
89-
}
90-
91-
function saveGeneratedCode(task: string, code: string) {
92-
readGeneratedFile().then((contents) => {
93-
contents = contents || {};
94-
95-
const key = getTestKey();
96-
contents[key] = contents[key] || {};
97-
contents[key][task] = code;
98-
99-
cy.writeFile(getGeneratedFilePath(), JSON.stringify(contents, null, 2), {
100-
log: false,
101-
});
102-
});
103-
}
104-
105-
Cypress.Commands.add('ai', (task) => {
106-
Cypress.log({ displayName: 'ai', message: task });
107-
108-
return readGeneratedFile().then((json) => {
109-
try {
110-
const code = json[getTestKey()][task];
111-
112-
if (code) {
113-
return eval(code);
114-
}
115-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-empty
116-
} catch (error) {}
117-
118-
cy.document({ log: false }).then(
119-
{ timeout: minutesToMilliseconds(2) },
120-
async (doc) => {
121-
const response = await chain.invoke({
122-
task,
123-
// html: sanitize(doc.documentElement.outerHTML),
124-
html: sanitize(doc.body.innerHTML),
125-
});
126-
// console.log(response)
127-
128-
const error = "I'm sorry, but I can't assist with that request.";
129-
if (response.includes(error)) {
130-
throw new Error(error);
131-
}
132-
133-
const code = response
134-
.match(/```(javascript|js)?([\s\S]+?)```/)?.[2]
135-
?.trim();
136-
if (code) {
137-
eval(code);
138-
saveGeneratedCode(task, code);
139-
}
140-
},
141-
);
142-
});
143-
});
144-
145-
declare global {
146-
// eslint-disable-next-line @typescript-eslint/no-namespace
147-
namespace Cypress {
148-
interface Chainable {
149-
ai(task: string): Chainable<void>;
150-
}
151-
}
152-
}
153-
154-
export {};
39+
import 'cy-ai';

package-lock.json

Lines changed: 16 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@
1212
"test": "cypress run"
1313
},
1414
"dependencies": {
15-
"@langchain/core": "0.3.57",
16-
"@langchain/ollama": "0.2.0",
17-
"cypress": "14.4.0",
18-
"dompurify": "3.2.6"
15+
"cy-ai": "0.5.0",
16+
"cypress": "14.4.0"
1917
},
2018
"devDependencies": {
2119
"@commitlint/cli": "19.8.1",

0 commit comments

Comments
 (0)