-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.html
More file actions
9 lines (9 loc) · 43.3 KB
/
Copy pathindex.html
File metadata and controls
9 lines (9 loc) · 43.3 KB
1
2
3
4
5
6
7
8
9
<!DOCTYPE html><link rel="stylesheet" href="/_astro/index.DaznmGaa.css"><script type="module" src="/_astro/page.DxliP9JT.js"></script><section id="examples" class="!mt-0 py-24 relative transition-colors duration-300"> <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 relative"> <div class="text-center mb-16"> <h2 class="text-4xl md:text-6xl font-bold mb-6 text-gray-900 dark:text-white">
Simple, yet <span class="headline-accent">professional</span>.
</h2> <p class="text-xl text-gray-500 dark:text-gray-400 max-w-2xl mx-auto">
Architecture and test design applied by default
</p> </div> <div class="max-w-6xl mx-auto"> <style>astro-island,astro-slot,astro-static-slot{display:contents}</style><script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event("astro:load"));})();</script><script>(()=>{var A=Object.defineProperty;var g=(i,o,a)=>o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=>g(i,typeof o!="symbol"?o+"":o,a);{let i={0:t=>m(t),1:t=>a(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(a(t)),5:t=>new Set(a(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>1/0*t},o=t=>{let[l,e]=t;return l in i?i[l](e):void 0},a=t=>t.map(o),m=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=>[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,"Component");d(this,"hydrator");d(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let c=this.querySelectorAll("astro-slot"),n={},h=this.querySelectorAll("template[data-astro-template]");for(let r of h){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("data-astro-template")||"default"]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("name")||"default"]=r.innerHTML)}let p;try{p=this.hasAttribute("props")?m(JSON.parse(this.getAttribute("props"))):{}}catch(r){let s=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute("props"),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});d(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute("opts")),c=this.getAttribute("client");if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=>this.start(),{once:!0});return}try{await Astro[c](async()=>{let n=this.getAttribute("renderer-url"),[h,{default:p}]=await Promise.all([import(this.getAttribute("component-url")),n?import(n):()=>()=>{}]),u=this.getAttribute("component-export")||"default";if(!u.includes("."))this.Component=h[u];else{this.Component=h;for(let f of u.split("."))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute("component-url")}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",y)}})();</script><astro-island uid="29OKrX" prefix="r1" component-url="/_astro/IDEPanel.DgncbvCt.js" component-export="default" renderer-url="/_astro/client.0xkaLLds.js" props="{"files":[1,[[0,{"path":[0,"codecept.conf.ts"],"name":[0,"codecept.conf.ts"],"language":[0,"typescript"],"code":[0,"import { setHeadlessWhen } from '@codeceptjs/configure';\n\nsetHeadlessWhen(process.env.HEADLESS);\n\nexport const config: CodeceptJS.MainConfig = {\n tests: './tests/**/*.test.ts',\n output: './output',\n helpers: {\n Playwright: {\n browser: 'chromium',\n url: process.env.URL || 'http://localhost:3000',\n show: true,\n },\n },\n include: {\n I: './steps_file.ts',\n milestonePage: './pages/Milestone.ts',\n taskPage: './pages/Task.ts',\n },\n plugins: {\n heal: { enabled: true },\n retryFailedStep: { enabled: true },\n auth: {\n enabled: true,\n saveToFile: true,\n users: {\n user: {\n login: (I) => I.login(),\n check: (I) => { I.amOnPage('/'); I.see('Dashboard', '.navbar'); },\n },\n },\n },\n },\n require: ['./support/heal.ts'],\n};"]}],[0,{"path":[0,"tests/milestones.test.ts"],"name":[0,"milestones.test.ts"],"language":[0,"typescript"],"code":[0,"Feature('Milestones');\n\nBefore(({ login }) => {\n login();\n});\n\nScenario('Create a milestone @smoke', ({ milestonePage }) => {\n milestonePage.open();\n milestonePage.create({ name: 'Q2 Release', dueDate: '2024-06-30' });\n milestonePage.checkVisible('Q2 Release');\n});\n\nScenario('Milestone tracks task progress', ({ I, milestonePage }) => {\n milestonePage.open();\n milestonePage.select('Q2 Release');\n I.see('0 / 5 tasks', '.progress');\n I.see('0%', '.progress-bar');\n});\n\nScenario('Complete a milestone', ({ I, milestonePage }) => {\n milestonePage.open();\n milestonePage.select('Q2 Release');\n I.click('Mark as Complete');\n I.see('Completed', '.milestone-status');\n});"]}],[0,{"path":[0,"tests/tasks.test.ts"],"name":[0,"tasks.test.ts"],"language":[0,"typescript"],"code":[0,"Feature('Task Management');\n\nBefore(({ login }) => {\n login();\n});\n\nScenario('Create and assign a task @smoke', ({ I, taskPage }) => {\n taskPage.openProject('Backend API');\n taskPage.create({\n title: 'Setup CI pipeline',\n assignee: 'Alice',\n milestone: 'Q2 Release',\n priority: 'high',\n });\n I.see('Setup CI pipeline', '.task-board');\n taskPage.checkAssignee('Setup CI pipeline', 'Alice');\n});\n\nScenario('Move task through statuses', ({ I, taskPage }) => {\n taskPage.openProject('Backend API');\n taskPage.setStatus('Setup CI pipeline', 'In Progress');\n I.see('In Progress', '.task-status');\n taskPage.setStatus('Setup CI pipeline', 'Done');\n I.see('1 / 5 tasks', '.milestone-progress');\n});"]}],[0,{"path":[0,"pages/Milestone.ts"],"name":[0,"Milestone.ts"],"language":[0,"typescript"],"code":[0,"const { I } = inject();\n\nclass MilestonePage {\n url = '/milestones';\n milestoneList = '.milestone-list';\n nameInput = '#milestone-name';\n dueDateInput = '#due-date';\n submitButton = locate('button').withText('Create Milestone');\n\n open() {\n I.amOnPage(this.url);\n I.waitForElement(this.milestoneList, 5);\n }\n\n create({ name, dueDate }: { name: string; dueDate: string }) {\n I.click('New Milestone');\n I.waitForElement(this.nameInput, 5);\n I.fillField(this.nameInput, name);\n I.fillField(this.dueDateInput, dueDate);\n I.click(this.submitButton);\n I.waitForElement(locate('.milestone-item').withText(name), 5);\n }\n\n select(name: string) {\n I.click(locate('.milestone-item').withText(name));\n I.waitForElement('.milestone-detail', 5);\n }\n\n checkVisible(name: string) {\n I.waitForElement(locate('.milestone-item').withText(name), 5);\n I.see(name, this.milestoneList);\n }\n}\n\nexport default MilestonePage"]}],[0,{"path":[0,"pages/Task.ts"],"name":[0,"Task.ts"],"language":[0,"typescript"],"code":[0,"const { I } = inject();\n\ninterface TaskInput {\n title: string;\n assignee?: string;\n milestone?: string;\n priority?: 'low' | 'medium' | 'high';\n}\n\nclass TaskPage {\n taskBoard = '.task-board';\n createButton = locate('button').withText('Add Task');\n titleInput = '#task-title';\n\n openProject(name: string) {\n I.amOnPage('/projects');\n I.click(locate('.project-card').withText(name));\n I.waitForElement(this.taskBoard, 10);\n }\n\n create(task: TaskInput) {\n I.click(this.createButton);\n I.waitForElement(this.titleInput, 5);\n I.fillField(this.titleInput, task.title);\n if (task.assignee) I.selectOption('#task-assignee', task.assignee);\n if (task.milestone) I.selectOption('#task-milestone', task.milestone);\n if (task.priority) I.selectOption('#task-priority', task.priority);\n I.click('Save Task');\n I.waitForElement(locate('.task-item').withText(task.title), 5);\n }\n\n setStatus(title: string, status: string) {\n I.click(locate('.task-item').withText(title));\n I.waitForElement('.task-detail', 5);\n I.selectOption('#task-status', status);\n I.click('Update');\n I.see(status, '.task-status');\n }\n\n checkAssignee(title: string, assignee: string) {\n I.see(assignee, locate('.task-item').withText(title));\n }\n}\n\nexport default TaskPage"]}],[0,{"path":[0,"support/heal.ts"],"name":[0,"heal.ts"],"language":[0,"typescript"],"code":[0,"import { heal } from 'codeceptjs';\n\n// Tests can randomly fails due to session expiration\n// if we see unauthorized message -> relogin and try prev step again\nheal.addRecipe('loginOnUnauthorized', {\n priority: 10,\n steps: ['click', 'see', 'amOnPage'],\n prepare: {\n url: ({ I }) => I.grabCurrentUrl(),\n html: ({ I }) => I.grabHTMLFrom('body'),\n },\n fn: async ({ url, error, html, step }) => {\n if (\n !url.includes('/login') &&\n !error.message.toLowerCase().includes('unauthorized') &&\n !html.toLowerCase().includes('Error: Unauthorized')\n ) return;\n\n return ({ I }) => {\n I.login();\n I[step.name](...step.args);\n };\n },\n});\n\n// When UX and HTML changes, flaky locator issues can be picked up by AI\n// AI will analyze HTML tree we passed in and will try to fix failing step\n// or replace it with a workaround and continue testing\nheal.addRecipe('ai', {\n priority: 20,\n steps: ['click', 'fillField', 'selectOption', 'checkOption', 'doubleClick'],\n prepare: {\n html: ({ I }) => I.grabHTMLFrom('body'),\n },\n fn: async (args) => {\n return ai.healFailedStep(args);\n },\n});"]}],[0,{"path":[0,"steps_file.ts"],"name":[0,"steps_file.ts"],"language":[0,"typescript"],"code":[0,"export default function () {\n return actor({\n login() {\n this.amOnPage('/login');\n this.fillField('#email', 'user@pm.app');\n this.fillField('#password', 'secret');\n this.click('Sign In');\n this.waitForElement('.dashboard', 10);\n },\n });\n}"]}]]],"tree":[1,[[0,{"path":[0,"codecept.conf.ts"],"name":[0,"codecept.conf.ts"],"language":[0,"typescript"],"code":[0,"import { setHeadlessWhen } from '@codeceptjs/configure';\n\nsetHeadlessWhen(process.env.HEADLESS);\n\nexport const config: CodeceptJS.MainConfig = {\n tests: './tests/**/*.test.ts',\n output: './output',\n helpers: {\n Playwright: {\n browser: 'chromium',\n url: process.env.URL || 'http://localhost:3000',\n show: true,\n },\n },\n include: {\n I: './steps_file.ts',\n milestonePage: './pages/Milestone.ts',\n taskPage: './pages/Task.ts',\n },\n plugins: {\n heal: { enabled: true },\n retryFailedStep: { enabled: true },\n auth: {\n enabled: true,\n saveToFile: true,\n users: {\n user: {\n login: (I) => I.login(),\n check: (I) => { I.amOnPage('/'); I.see('Dashboard', '.navbar'); },\n },\n },\n },\n },\n require: ['./support/heal.ts'],\n};"]}],[0,{"name":[0,"tests"],"path":[0,"tests"],"children":[1,[[0,{"path":[0,"tests/milestones.test.ts"],"name":[0,"milestones.test.ts"],"language":[0,"typescript"],"code":[0,"Feature('Milestones');\n\nBefore(({ login }) => {\n login();\n});\n\nScenario('Create a milestone @smoke', ({ milestonePage }) => {\n milestonePage.open();\n milestonePage.create({ name: 'Q2 Release', dueDate: '2024-06-30' });\n milestonePage.checkVisible('Q2 Release');\n});\n\nScenario('Milestone tracks task progress', ({ I, milestonePage }) => {\n milestonePage.open();\n milestonePage.select('Q2 Release');\n I.see('0 / 5 tasks', '.progress');\n I.see('0%', '.progress-bar');\n});\n\nScenario('Complete a milestone', ({ I, milestonePage }) => {\n milestonePage.open();\n milestonePage.select('Q2 Release');\n I.click('Mark as Complete');\n I.see('Completed', '.milestone-status');\n});"]}],[0,{"path":[0,"tests/tasks.test.ts"],"name":[0,"tasks.test.ts"],"language":[0,"typescript"],"code":[0,"Feature('Task Management');\n\nBefore(({ login }) => {\n login();\n});\n\nScenario('Create and assign a task @smoke', ({ I, taskPage }) => {\n taskPage.openProject('Backend API');\n taskPage.create({\n title: 'Setup CI pipeline',\n assignee: 'Alice',\n milestone: 'Q2 Release',\n priority: 'high',\n });\n I.see('Setup CI pipeline', '.task-board');\n taskPage.checkAssignee('Setup CI pipeline', 'Alice');\n});\n\nScenario('Move task through statuses', ({ I, taskPage }) => {\n taskPage.openProject('Backend API');\n taskPage.setStatus('Setup CI pipeline', 'In Progress');\n I.see('In Progress', '.task-status');\n taskPage.setStatus('Setup CI pipeline', 'Done');\n I.see('1 / 5 tasks', '.milestone-progress');\n});"]}]]]}],[0,{"name":[0,"pages"],"path":[0,"pages"],"children":[1,[[0,{"path":[0,"pages/Milestone.ts"],"name":[0,"Milestone.ts"],"language":[0,"typescript"],"code":[0,"const { I } = inject();\n\nclass MilestonePage {\n url = '/milestones';\n milestoneList = '.milestone-list';\n nameInput = '#milestone-name';\n dueDateInput = '#due-date';\n submitButton = locate('button').withText('Create Milestone');\n\n open() {\n I.amOnPage(this.url);\n I.waitForElement(this.milestoneList, 5);\n }\n\n create({ name, dueDate }: { name: string; dueDate: string }) {\n I.click('New Milestone');\n I.waitForElement(this.nameInput, 5);\n I.fillField(this.nameInput, name);\n I.fillField(this.dueDateInput, dueDate);\n I.click(this.submitButton);\n I.waitForElement(locate('.milestone-item').withText(name), 5);\n }\n\n select(name: string) {\n I.click(locate('.milestone-item').withText(name));\n I.waitForElement('.milestone-detail', 5);\n }\n\n checkVisible(name: string) {\n I.waitForElement(locate('.milestone-item').withText(name), 5);\n I.see(name, this.milestoneList);\n }\n}\n\nexport default MilestonePage"]}],[0,{"path":[0,"pages/Task.ts"],"name":[0,"Task.ts"],"language":[0,"typescript"],"code":[0,"const { I } = inject();\n\ninterface TaskInput {\n title: string;\n assignee?: string;\n milestone?: string;\n priority?: 'low' | 'medium' | 'high';\n}\n\nclass TaskPage {\n taskBoard = '.task-board';\n createButton = locate('button').withText('Add Task');\n titleInput = '#task-title';\n\n openProject(name: string) {\n I.amOnPage('/projects');\n I.click(locate('.project-card').withText(name));\n I.waitForElement(this.taskBoard, 10);\n }\n\n create(task: TaskInput) {\n I.click(this.createButton);\n I.waitForElement(this.titleInput, 5);\n I.fillField(this.titleInput, task.title);\n if (task.assignee) I.selectOption('#task-assignee', task.assignee);\n if (task.milestone) I.selectOption('#task-milestone', task.milestone);\n if (task.priority) I.selectOption('#task-priority', task.priority);\n I.click('Save Task');\n I.waitForElement(locate('.task-item').withText(task.title), 5);\n }\n\n setStatus(title: string, status: string) {\n I.click(locate('.task-item').withText(title));\n I.waitForElement('.task-detail', 5);\n I.selectOption('#task-status', status);\n I.click('Update');\n I.see(status, '.task-status');\n }\n\n checkAssignee(title: string, assignee: string) {\n I.see(assignee, locate('.task-item').withText(title));\n }\n}\n\nexport default TaskPage"]}]]]}],[0,{"name":[0,"support"],"path":[0,"support"],"children":[1,[[0,{"path":[0,"support/heal.ts"],"name":[0,"heal.ts"],"language":[0,"typescript"],"code":[0,"import { heal } from 'codeceptjs';\n\n// Tests can randomly fails due to session expiration\n// if we see unauthorized message -> relogin and try prev step again\nheal.addRecipe('loginOnUnauthorized', {\n priority: 10,\n steps: ['click', 'see', 'amOnPage'],\n prepare: {\n url: ({ I }) => I.grabCurrentUrl(),\n html: ({ I }) => I.grabHTMLFrom('body'),\n },\n fn: async ({ url, error, html, step }) => {\n if (\n !url.includes('/login') &&\n !error.message.toLowerCase().includes('unauthorized') &&\n !html.toLowerCase().includes('Error: Unauthorized')\n ) return;\n\n return ({ I }) => {\n I.login();\n I[step.name](...step.args);\n };\n },\n});\n\n// When UX and HTML changes, flaky locator issues can be picked up by AI\n// AI will analyze HTML tree we passed in and will try to fix failing step\n// or replace it with a workaround and continue testing\nheal.addRecipe('ai', {\n priority: 20,\n steps: ['click', 'fillField', 'selectOption', 'checkOption', 'doubleClick'],\n prepare: {\n html: ({ I }) => I.grabHTMLFrom('body'),\n },\n fn: async (args) => {\n return ai.healFailedStep(args);\n },\n});"]}]]]}],[0,{"path":[0,"steps_file.ts"],"name":[0,"steps_file.ts"],"language":[0,"typescript"],"code":[0,"export default function () {\n return actor({\n login() {\n this.amOnPage('/login');\n this.fillField('#email', 'user@pm.app');\n this.fillField('#password', 'secret');\n this.click('Sign In');\n this.waitForElement('.dashboard', 10);\n },\n });\n}"]}]]],"defaultFile":[0,"tests/milestones.test.ts"]}" ssr client="load" opts="{"name":"IDEPanel","value":true}" await-children><div class="ide-panel overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 shadow-2xl bg-white dark:bg-gray-900"><div class="flex min-h-[320px] md:min-h-[420px]"><div class="hidden w-56 shrink-0 border-r border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 overflow-y-auto md:block"><div class="font-mono border-0 rounded-none bg-transparent text-xs" role="tree"><div class="p-2"><div class="flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50" role="treeitem" tabindex="0"><span class="size-4 shrink-0"></span><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file size-4 text-muted-foreground" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path></svg></span><span class="truncate">codecept.conf.ts</span></div><div data-state="open" data-slot="collapsible"><div class="" role="treeitem" tabindex="0"><div class="flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50"><button class="flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0" type="button" aria-controls="radix-_r1R_9_" aria-expanded="true" data-state="open" data-slot="collapsible-trigger"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right size-4 shrink-0 text-muted-foreground transition-transform rotate-90" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg></button><button class="flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left" type="button"><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-open size-4 text-blue-500" aria-hidden="true"><path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"></path></svg></span><span class="truncate">tests</span></button></div><div data-state="open" id="radix-_r1R_9_" data-slot="collapsible-content"><div class="ml-4 border-l pl-2"><div class="flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50 bg-muted" role="treeitem" tabindex="0"><span class="size-4 shrink-0"></span><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file size-4 text-muted-foreground" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path></svg></span><span class="truncate">milestones.test.ts</span></div><div class="flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50" role="treeitem" tabindex="0"><span class="size-4 shrink-0"></span><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file size-4 text-muted-foreground" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path></svg></span><span class="truncate">tasks.test.ts</span></div></div></div></div></div><div data-state="open" data-slot="collapsible"><div class="" role="treeitem" tabindex="0"><div class="flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50"><button class="flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0" type="button" aria-controls="radix-_r1R_d_" aria-expanded="true" data-state="open" data-slot="collapsible-trigger"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right size-4 shrink-0 text-muted-foreground transition-transform rotate-90" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg></button><button class="flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left" type="button"><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-open size-4 text-blue-500" aria-hidden="true"><path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"></path></svg></span><span class="truncate">pages</span></button></div><div data-state="open" id="radix-_r1R_d_" data-slot="collapsible-content"><div class="ml-4 border-l pl-2"><div class="flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50" role="treeitem" tabindex="0"><span class="size-4 shrink-0"></span><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file size-4 text-muted-foreground" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path></svg></span><span class="truncate">Milestone.ts</span></div><div class="flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50" role="treeitem" tabindex="0"><span class="size-4 shrink-0"></span><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file size-4 text-muted-foreground" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path></svg></span><span class="truncate">Task.ts</span></div></div></div></div></div><div data-state="open" data-slot="collapsible"><div class="" role="treeitem" tabindex="0"><div class="flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50"><button class="flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0" type="button" aria-controls="radix-_r1R_h_" aria-expanded="true" data-state="open" data-slot="collapsible-trigger"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right size-4 shrink-0 text-muted-foreground transition-transform rotate-90" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg></button><button class="flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left" type="button"><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-open size-4 text-blue-500" aria-hidden="true"><path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"></path></svg></span><span class="truncate">support</span></button></div><div data-state="open" id="radix-_r1R_h_" data-slot="collapsible-content"><div class="ml-4 border-l pl-2"><div class="flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50" role="treeitem" tabindex="0"><span class="size-4 shrink-0"></span><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file size-4 text-muted-foreground" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path></svg></span><span class="truncate">heal.ts</span></div></div></div></div></div><div class="flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50" role="treeitem" tabindex="0"><span class="size-4 shrink-0"></span><span class="shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file size-4 text-muted-foreground" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path></svg></span><span class="truncate">steps_file.ts</span></div></div></div></div><div class="flex-1 min-w-0 flex flex-col"><div class="group relative w-full overflow-hidden bg-background text-foreground border-0 rounded-none flex-1" data-language="typescript" style="contain-intrinsic-size:auto 200px;content-visibility:auto"><div class="flex items-center justify-between border-b bg-muted/80 px-3 py-2 text-muted-foreground text-xs"><div class="flex items-center gap-2"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path></svg><span class="font-mono">milestones.test.ts</span></div><div class="-my-1 -mr-1 flex items-center gap-2"><button data-slot="button" data-variant="ghost" data-size="icon" class="inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 size-9 shrink-0"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></div></div><div class="relative overflow-auto"><pre class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)] m-0 p-3 text-sm sm:p-4" style="background-color:transparent;color:inherit"><code class="font-mono text-sm [counter-increment:line_0] [counter-reset:line]"><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#7c3aed;font-weight:600;--shiki-dark:#d8b4fe">Feature</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">('Milestones');</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none">
</span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#7c3aed;font-weight:600;--shiki-dark:#d8b4fe">Before</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">(({ login }) => {</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> login();</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">});</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none">
</span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#7c3aed;font-weight:600;--shiki-dark:#d8b4fe">Scenario</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">('Create a milestone @smoke', ({ milestonePage }) => {</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> milestonePage.open();</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> milestonePage.create({ name: 'Q2 Release', dueDate: '2024-06-30' });</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> milestonePage.checkVisible('Q2 Release');</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">});</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none">
</span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#7c3aed;font-weight:600;--shiki-dark:#d8b4fe">Scenario</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">('Milestone tracks task progress', ({ </span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#db2777;font-weight:600;--shiki-dark:#f472b6">I</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">, milestonePage }) => {</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> milestonePage.open();</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> milestonePage.select('Q2 Release');</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> </span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#db2777;font-weight:600;--shiki-dark:#f472b6">I</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">.see('0 / 5 tasks', '.progress');</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> </span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#db2777;font-weight:600;--shiki-dark:#f472b6">I</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">.see('0%', '.progress-bar');</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">});</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none">
</span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#7c3aed;font-weight:600;--shiki-dark:#d8b4fe">Scenario</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">('Complete a milestone', ({ </span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#db2777;font-weight:600;--shiki-dark:#f472b6">I</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">, milestonePage }) => {</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> milestonePage.open();</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> milestonePage.select('Q2 Release');</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> </span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#db2777;font-weight:600;--shiki-dark:#f472b6">I</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">.click('Mark as Complete');</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit"> </span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:#db2777;font-weight:600;--shiki-dark:#f472b6">I</span><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">.see('Completed', '.milestone-status');</span></span><span class="block before:content-[counter(line)] before:inline-block before:[counter-increment:line] before:w-8 before:mr-4 before:text-right before:text-muted-foreground/50 before:font-mono before:select-none"><span class="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]" style="color:inherit">});</span></span></code></pre></div></div></div></div></div><!--astro:end--></astro-island> </div> </div> </section>