@probolabs/playwright 0.4.11 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,136 +1,136 @@
1
- # Probolib: drive your playwright scripts with AI superpowers
2
-
3
- Probolib is a powerful AI-driven automation library that enhances Playwright testing and automation by making your scripts more robust and maintainable. Instead of relying on brittle CSS selectors or complex XPath expressions, Probolib uses natural language to interact with web elements.
4
-
5
- ## Why Probolib?
6
-
7
- - **Natural Language Automation**: Write human-readable instructions instead of complex selectors
8
- - **More Resilient Tests**: AI-powered element detection that adapts to UI changes
9
- - **Simpler Maintenance**: Reduce the need to update selectors when the UI changes
10
- - **Faster Development**: Write automation scripts in plain English
11
-
12
- ## Example
13
-
14
- Instead of writing:
15
-
16
- ```
17
- page.click('some > super > complex > css > and non robust selector')
18
- ```
19
-
20
- You can simply write:
21
-
22
- ```
23
- probo.runStep(page, 'click on the save button')
24
- ```
25
-
26
- ## Quickstart
27
-
28
- ```
29
- npm install @probolabs/playwright
30
- ```
31
-
32
- ## Accessing our backend
33
-
34
- the heavy lifting of the AI reasoning is done at the moment on our backend server. in order to access it you would need to tell the library how to access it.
35
-
36
- ## easyiest - set ENV var
37
-
38
- probolib expects the existance of 2 env vars:
39
-
40
- ```
41
- export PROBO_API_ENDPOINT=api.probolabs.ai
42
- export PROBO_API_KEY=<api key we will give you>
43
-
44
- ```
45
-
46
- Or use dotenv
47
-
48
- ```
49
- // npm install dotenv
50
- // in your script add this line
51
- import 'dotenv/config' // ES6
52
-
53
- // .env file located in the root of your project (the same level as your package.json)
54
- PROBO_API_ENDPOINT=https://api.probolabs.ai
55
- PROBO_API_KEY=<api key we will give you>
56
-
57
- ```
58
-
59
- ## A complete example for Playwright integration. step by step
60
-
61
- **step 1:** init a playwright project
62
-
63
- ```
64
- # from your work directory
65
- mkdir probo-example
66
- cd probo-example
67
- npm init playwright@latest
68
- # follow the instructions and wait till the installation is finished
69
- # verify that playwright is installed properly
70
- npx playwright test --project chromium
71
- ```
72
-
73
- **step 2**: integrate probolabs
74
-
75
- ```
76
- npm install @probolabs/playwright dotenv
77
- ```
78
-
79
- **step 3:** configure the env with our endpoint and api key
80
-
81
- ```
82
- touch .env
83
- ```
84
-
85
- edit the .env file
86
-
87
- ```
88
- #.env
89
- PROBO_API_ENDPOINT=https://api.probolabs.ai
90
- PROBO_API_KEY=<api key we will give you>
91
-
92
- ```
93
-
94
- **step 4:** create a file under the tests folder named `probo-example-todo-mvc.spec.mjs`
95
-
96
- ```
97
- // tests/probo-example-todo-mvc.spec.mjs
98
- import 'dotenv/config';
99
- import { test} from '@playwright/test';
100
- import { Probo } from '@probolabs/playwright';
101
-
102
- //
103
- // Important: Before running this script set PROBO_API_KEY and PROBO_API_ENDPOINT
104
- //
105
-
106
- test.describe('Todo MVC', () => {
107
- test('basic example', async ({ page }) => {
108
- try {
109
- // Initialize Probo
110
- const probo = new Probo({
111
- scenarioName: 'probo-example-todo-mvc' // important for caching the AI reasoning
112
- });
113
-
114
- //Goto page
115
- await page.goto('https://demo.playwright.dev/todomvc');
116
-
117
- // Run test steps
118
- console.log('Running test steps...');
119
- await probo.runStep(page, 'enter a new todo item: "Buy groceries"');
120
- await probo.runStep(page, 'press the Enter key');
121
-
122
- console.log('✨ Test completed successfully!');
123
- } catch (error) {
124
- console.error('❌ Test failed:', error);
125
- throw error; // Re-throw to mark the test as failed
126
- }
127
- });
128
- });
129
-
130
- ```
131
-
132
- **run the example**
133
-
134
- ```
135
- npx playwright test tests/probo-example-todo-mvc.spec.mjs --headed --project chromium
136
- ```
1
+ # Probolib: drive your playwright scripts with AI superpowers
2
+
3
+ Probolib is a powerful AI-driven automation library that enhances Playwright testing and automation by making your scripts more robust and maintainable. Instead of relying on brittle CSS selectors or complex XPath expressions, Probolib uses natural language to interact with web elements.
4
+
5
+ ## Why Probolib?
6
+
7
+ - **Natural Language Automation**: Write human-readable instructions instead of complex selectors
8
+ - **More Resilient Tests**: AI-powered element detection that adapts to UI changes
9
+ - **Simpler Maintenance**: Reduce the need to update selectors when the UI changes
10
+ - **Faster Development**: Write automation scripts in plain English
11
+
12
+ ## Example
13
+
14
+ Instead of writing:
15
+
16
+ ```
17
+ page.click('some > super > complex > css > and non robust selector')
18
+ ```
19
+
20
+ You can simply write:
21
+
22
+ ```
23
+ probo.runStep(page, 'click on the save button')
24
+ ```
25
+
26
+ ## Quickstart
27
+
28
+ ```
29
+ npm install @probolabs/playwright
30
+ ```
31
+
32
+ ## Accessing our backend
33
+
34
+ the heavy lifting of the AI reasoning is done at the moment on our backend server. in order to access it you would need to tell the library how to access it.
35
+
36
+ ## easyiest - set ENV var
37
+
38
+ probolib expects the existance of 2 env vars:
39
+
40
+ ```
41
+ export PROBO_API_ENDPOINT=api.probolabs.ai
42
+ export PROBO_API_KEY=<api key we will give you>
43
+
44
+ ```
45
+
46
+ Or use dotenv
47
+
48
+ ```
49
+ // npm install dotenv
50
+ // in your script add this line
51
+ import 'dotenv/config' // ES6
52
+
53
+ // .env file located in the root of your project (the same level as your package.json)
54
+ PROBO_API_ENDPOINT=https://api.probolabs.ai
55
+ PROBO_API_KEY=<api key we will give you>
56
+
57
+ ```
58
+
59
+ ## A complete example for Playwright integration. step by step
60
+
61
+ **step 1:** init a playwright project
62
+
63
+ ```
64
+ # from your work directory
65
+ mkdir probo-example
66
+ cd probo-example
67
+ npm init playwright@latest
68
+ # follow the instructions and wait till the installation is finished
69
+ # verify that playwright is installed properly
70
+ npx playwright test --project chromium
71
+ ```
72
+
73
+ **step 2**: integrate probolabs
74
+
75
+ ```
76
+ npm install @probolabs/playwright dotenv
77
+ ```
78
+
79
+ **step 3:** configure the env with our endpoint and api key
80
+
81
+ ```
82
+ touch .env
83
+ ```
84
+
85
+ edit the .env file
86
+
87
+ ```
88
+ #.env
89
+ PROBO_API_ENDPOINT=https://api.probolabs.ai
90
+ PROBO_API_KEY=<api key we will give you>
91
+
92
+ ```
93
+
94
+ **step 4:** create a file under the tests folder named `probo-example-todo-mvc.spec.mjs`
95
+
96
+ ```
97
+ // tests/probo-example-todo-mvc.spec.mjs
98
+ import 'dotenv/config';
99
+ import { test} from '@playwright/test';
100
+ import { Probo } from '@probolabs/playwright';
101
+
102
+ //
103
+ // Important: Before running this script set PROBO_API_KEY and PROBO_API_ENDPOINT
104
+ //
105
+
106
+ test.describe('Todo MVC', () => {
107
+ test('basic example', async ({ page }) => {
108
+ try {
109
+ // Initialize Probo
110
+ const probo = new Probo({
111
+ scenarioName: 'probo-example-todo-mvc' // important for caching the AI reasoning
112
+ });
113
+
114
+ //Goto page
115
+ await page.goto('https://demo.playwright.dev/todomvc');
116
+
117
+ // Run test steps
118
+ console.log('Running test steps...');
119
+ await probo.runStep(page, 'enter a new todo item: "Buy groceries"');
120
+ await probo.runStep(page, 'press the Enter key');
121
+
122
+ console.log('✨ Test completed successfully!');
123
+ } catch (error) {
124
+ console.error('❌ Test failed:', error);
125
+ throw error; // Re-throw to mark the test as failed
126
+ }
127
+ });
128
+ });
129
+
130
+ ```
131
+
132
+ **run the example**
133
+
134
+ ```
135
+ npx playwright test tests/probo-example-todo-mvc.spec.mjs --headed --project chromium
136
+ ```
@@ -0,0 +1,24 @@
1
+ import { Page } from 'playwright';
2
+ export declare function waitForNavigationToSettle(page: Page, options?: {
3
+ initialWait?: number;
4
+ navigationTimeout?: number;
5
+ totalTimeout?: number;
6
+ }): Promise<boolean>;
7
+ export declare function stabilizePage(page: Page): Promise<void>;
8
+ export declare const PlaywrightAction: {
9
+ readonly VISIT_BASE_URL: "VISIT_BASE_URL";
10
+ readonly VISIT_URL: "VISIT_URL";
11
+ readonly CLICK: "CLICK";
12
+ readonly FILL_IN: "FILL_IN";
13
+ readonly SELECT_DROPDOWN: "SELECT_DROPDOWN";
14
+ readonly SELECT_MULTIPLE_DROPDOWN: "SELECT_MULTIPLE_DROPDOWN";
15
+ readonly CHECK_CHECKBOX: "CHECK_CHECKBOX";
16
+ readonly SELECT_RADIO: "SELECT_RADIO";
17
+ readonly TOGGLE_SWITCH: "TOGGLE_SWITCH";
18
+ readonly TYPE_KEYS: "TYPE_KEYS";
19
+ readonly VALIDATE_EXACT_VALUE: "VALIDATE_EXACT_VALUE";
20
+ readonly VALIDATE_CONTAINS_VALUE: "VALIDATE_CONTAINS_VALUE";
21
+ readonly VALIDATE_URL: "VALIDATE_URL";
22
+ };
23
+ export type PlaywrightActionType = typeof PlaywrightAction[keyof typeof PlaywrightAction];
24
+ export declare const executePlaywrightAction: (page: Page, action: PlaywrightActionType, value: string, element_css_selector: string) => Promise<boolean>;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAS,MAAM,YAAY,CAAC;AAmMzC,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;CAcnB,CAAC;AAGX,MAAM,MAAM,oBAAoB,GAAG,OAAO,gBAAgB,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAE1F,eAAO,MAAM,uBAAuB,SAC5B,IAAI,UACF,oBAAoB,SACrB,MAAM,wBACS,MAAM,qBAiF7B,CAAA"}
@@ -0,0 +1,241 @@
1
+ import { proboLogger } from './utils';
2
+ // Utility functions for enhanced Playwright interactions
3
+ async function handleNavigation(page, url, navigationTimeout = 5000, totalTimeout = 10000) {
4
+ let navigationCount = 0;
5
+ let lastNavigationTime = null;
6
+ const startTime = Date.now();
7
+ // Declare the function outside try block so it's accessible in finally
8
+ const onFrameNavigated = (frame) => {
9
+ if (frame === page.mainFrame()) {
10
+ navigationCount++;
11
+ lastNavigationTime = Date.now();
12
+ proboLogger.debug(`Navigation ${navigationCount} detected at ${lastNavigationTime}`);
13
+ }
14
+ };
15
+ try {
16
+ // Setup navigation tracking
17
+ page.on('framenavigated', onFrameNavigated);
18
+ await page.goto(url, {
19
+ waitUntil: 'domcontentloaded',
20
+ timeout: totalTimeout
21
+ });
22
+ // Final wait only for DOM, not network
23
+ if (navigationCount > 0) {
24
+ proboLogger.debug(`Total navigations detected: ${navigationCount}`);
25
+ await page.waitForLoadState('domcontentloaded');
26
+ // Add scroll to bottom to trigger lazy loading
27
+ await scrollToBottomRight(page);
28
+ }
29
+ }
30
+ finally {
31
+ page.removeListener('framenavigated', onFrameNavigated);
32
+ proboLogger.debug('Navigation listener removed');
33
+ }
34
+ }
35
+ async function enhancedClick(page, selector, options = {}) {
36
+ // Track initial scroll position
37
+ let initScrollY = await page.evaluate(() => window.scrollY);
38
+ let initScrollX = await page.evaluate(() => window.scrollX);
39
+ // Perform the click
40
+ await page.click(selector, { noWaitAfter: false, ...options });
41
+ // Monitor for any scrolling that might occur due to the click
42
+ let scrollingDetected = false;
43
+ while (true) {
44
+ const currScrollX = await page.evaluate(() => window.scrollX);
45
+ const currScrollY = await page.evaluate(() => window.scrollY);
46
+ if (currScrollY !== initScrollY || currScrollX !== initScrollX) {
47
+ proboLogger.debug('Click detected scrolling, waiting 50ms for stabilization...');
48
+ scrollingDetected = true;
49
+ await page.waitForTimeout(50);
50
+ initScrollX = currScrollX;
51
+ initScrollY = currScrollY;
52
+ }
53
+ else {
54
+ break;
55
+ }
56
+ }
57
+ // If scrolling occurred, reset to top
58
+ if (scrollingDetected) {
59
+ await page.evaluate(() => window.scrollTo(0, 0));
60
+ await page.waitForTimeout(50); // Small delay between scrolls
61
+ }
62
+ }
63
+ async function selectDropdownOption(page, selector, value) {
64
+ // Get the element's tag name
65
+ const locator = page.locator(selector);
66
+ const tagName = await locator.evaluate(el => el.tagName.toLowerCase());
67
+ if (tagName === 'select') {
68
+ // Handle native select element
69
+ proboLogger.debug('selectDropdownOption: simple select tag');
70
+ await page.selectOption(selector, value);
71
+ }
72
+ else { // not a native select
73
+ try {
74
+ // Look for child <select> if exists
75
+ const listboxSelector = locator.locator('select');
76
+ const count = await listboxSelector.count();
77
+ if (count === 1) { // delegate to child <select>
78
+ proboLogger.debug('Identified child element with tag select');
79
+ await listboxSelector.selectOption(value);
80
+ }
81
+ else {
82
+ // Click the original locator to expose the real listbox and options
83
+ proboLogger.debug('Clicking container element, looking for child element with role listbox');
84
+ await locator.click();
85
+ const listbox = locator.getByRole('listbox');
86
+ proboLogger.debug(`Clicking option ${value}`);
87
+ // Handle both string and array values
88
+ const values = Array.isArray(value) ? value : [value];
89
+ for (const val of values) {
90
+ const optionSelector = listbox
91
+ .getByRole('option')
92
+ .getByText(new RegExp(`^${val.toLowerCase()}$`, 'i'));
93
+ const optionCount = await optionSelector.count();
94
+ if (optionCount !== 1) {
95
+ throw new Error(`Found ${optionCount} option selectors for value: ${val}`);
96
+ }
97
+ await optionSelector.click();
98
+ }
99
+ }
100
+ }
101
+ catch (error) {
102
+ throw new Error(`Failed to select option '${value}' from custom dropdown: ${error}`);
103
+ }
104
+ }
105
+ }
106
+ async function getElementValue(page, selector) {
107
+ // TODO: Implement getting all text contents
108
+ proboLogger.log("TODO: Implement getting all text contents");
109
+ return "TODO: Implement getting all text contents";
110
+ }
111
+ async function waitForMutationsToSettle(page) {
112
+ // TODO: Implement mutation observer
113
+ //proboLogger.log("TODO: Implement mutation observer");
114
+ await page.waitForTimeout(500);
115
+ }
116
+ async function scrollToBottomRight(page) {
117
+ proboLogger.debug("Scrolling to bottom of page...");
118
+ // Get initial page dimensions
119
+ let lastHeight = await page.evaluate(() => document.documentElement.scrollHeight);
120
+ let lastWidth = await page.evaluate(() => document.documentElement.scrollWidth);
121
+ while (true) {
122
+ let smoothingSteps = 0;
123
+ // Get current scroll positions and dimensions
124
+ const initScrollY = await page.evaluate(() => window.scrollY);
125
+ const initScrollX = await page.evaluate(() => window.scrollX);
126
+ const maxScrollY = await page.evaluate(() => document.documentElement.scrollHeight);
127
+ const maxScrollX = await page.evaluate(() => document.documentElement.scrollWidth);
128
+ const maxClientY = await page.evaluate(() => document.documentElement.clientHeight);
129
+ const maxClientX = await page.evaluate(() => document.documentElement.clientWidth);
130
+ // Scroll both horizontally and vertically in steps
131
+ let currScrollX = initScrollX;
132
+ while (currScrollX <= (maxScrollX - maxClientX)) {
133
+ let currScrollY = initScrollY;
134
+ while (currScrollY <= (maxScrollY - maxClientY)) {
135
+ currScrollY += maxClientY;
136
+ await page.evaluate(({ x, y }) => window.scrollTo(x, y), { x: currScrollX, y: currScrollY });
137
+ await page.waitForTimeout(50); // Small delay between scrolls
138
+ smoothingSteps++;
139
+ }
140
+ currScrollX += maxClientX;
141
+ }
142
+ proboLogger.debug(`Performed ${smoothingSteps} smoothing steps while scrolling`);
143
+ // Check if content was lazy loaded (dimensions changed)
144
+ const newHeight = await page.evaluate(() => document.documentElement.scrollHeight);
145
+ const newWidth = await page.evaluate(() => document.documentElement.scrollWidth);
146
+ if (newHeight === lastHeight && newWidth === lastWidth) {
147
+ break;
148
+ }
149
+ proboLogger.debug("Page dimensions updated, repeating smoothing steps with new dimensions");
150
+ lastHeight = newHeight;
151
+ lastWidth = newWidth;
152
+ }
153
+ // Reset scroll position to top left
154
+ await page.waitForTimeout(200);
155
+ await page.evaluate(() => window.scrollTo(0, 0));
156
+ await page.waitForTimeout(50);
157
+ }
158
+ export const PlaywrightAction = {
159
+ VISIT_BASE_URL: 'VISIT_BASE_URL',
160
+ VISIT_URL: 'VISIT_URL',
161
+ CLICK: 'CLICK',
162
+ FILL_IN: 'FILL_IN',
163
+ SELECT_DROPDOWN: 'SELECT_DROPDOWN',
164
+ SELECT_MULTIPLE_DROPDOWN: 'SELECT_MULTIPLE_DROPDOWN',
165
+ CHECK_CHECKBOX: 'CHECK_CHECKBOX',
166
+ SELECT_RADIO: 'SELECT_RADIO',
167
+ TOGGLE_SWITCH: 'TOGGLE_SWITCH',
168
+ PRESS_KEY: 'PRESS_KEY',
169
+ VALIDATE_EXACT_VALUE: 'VALIDATE_EXACT_VALUE',
170
+ VALIDATE_CONTAINS_VALUE: 'VALIDATE_CONTAINS_VALUE',
171
+ VALIDATE_URL: 'VALIDATE_URL'
172
+ };
173
+ export const executePlaywrightAction = async (page, action, value, element_css_selector) => {
174
+ proboLogger.info('Executing playwright action:', { action, value, element_css_selector });
175
+ try {
176
+ switch (action) {
177
+ case PlaywrightAction.VISIT_BASE_URL:
178
+ case PlaywrightAction.VISIT_URL:
179
+ await handleNavigation(page, value);
180
+ break;
181
+ case PlaywrightAction.CLICK:
182
+ await enhancedClick(page, element_css_selector);
183
+ break;
184
+ case PlaywrightAction.FILL_IN:
185
+ await enhancedClick(page, element_css_selector);
186
+ await page.fill(element_css_selector, value);
187
+ break;
188
+ case PlaywrightAction.SELECT_DROPDOWN:
189
+ await selectDropdownOption(page, element_css_selector, value);
190
+ break;
191
+ case PlaywrightAction.SELECT_MULTIPLE_DROPDOWN:
192
+ const options = value.split(',').map(opt => opt.trim());
193
+ await selectDropdownOption(page, element_css_selector, options);
194
+ break;
195
+ case PlaywrightAction.CHECK_CHECKBOX:
196
+ const checked = value.toLowerCase() === 'true';
197
+ await page.setChecked(element_css_selector, checked);
198
+ break;
199
+ case PlaywrightAction.SELECT_RADIO:
200
+ case PlaywrightAction.TOGGLE_SWITCH:
201
+ await enhancedClick(page, element_css_selector);
202
+ break;
203
+ case PlaywrightAction.PRESS_KEY:
204
+ await page.press(element_css_selector, value);
205
+ break;
206
+ case PlaywrightAction.VALIDATE_EXACT_VALUE:
207
+ const exactValue = await getElementValue(page, element_css_selector);
208
+ if (exactValue !== value) {
209
+ proboLogger.info(`Validation FAIL: expected ${value} but got ${exactValue}`);
210
+ return false;
211
+ }
212
+ proboLogger.info('Validation PASS');
213
+ return true;
214
+ case PlaywrightAction.VALIDATE_CONTAINS_VALUE:
215
+ const containsValue = await getElementValue(page, element_css_selector);
216
+ if (!containsValue.includes(value)) {
217
+ proboLogger.info(`Validation FAIL: expected ${value} to be contained in ${containsValue}`);
218
+ return false;
219
+ }
220
+ proboLogger.info('Validation PASS');
221
+ return true;
222
+ case PlaywrightAction.VALIDATE_URL:
223
+ const currentUrl = page.url();
224
+ if (currentUrl !== value) {
225
+ proboLogger.info(`Validation FAIL: expected url ${value} but got ${currentUrl}`);
226
+ return false;
227
+ }
228
+ proboLogger.info('Validation PASS');
229
+ return true;
230
+ default:
231
+ throw new Error(`Unknown action: ${action}`);
232
+ }
233
+ // Wait for mutations to settle after any action
234
+ await waitForMutationsToSettle(page);
235
+ return true;
236
+ }
237
+ catch (error) {
238
+ proboLogger.error(`Failed to execute action ${action}:`, error);
239
+ throw error;
240
+ }
241
+ };
@@ -0,0 +1,32 @@
1
+ export interface Instruction {
2
+ what_to_do: string;
3
+ args: any;
4
+ result: any;
5
+ }
6
+ export interface CreateStepOptions {
7
+ stepIdFromServer?: number | null;
8
+ scenarioName: string;
9
+ stepPrompt: string;
10
+ initial_screenshot_url: string;
11
+ initial_html_content: string;
12
+ use_cache: boolean;
13
+ }
14
+ export declare class ApiError extends Error {
15
+ status: number;
16
+ data?: any | undefined;
17
+ constructor(status: number, message: string, data?: any | undefined);
18
+ toString(): string;
19
+ }
20
+ export declare class ApiClient {
21
+ private apiUrl;
22
+ private token;
23
+ private maxRetries;
24
+ private initialBackoff;
25
+ constructor(apiUrl: string, token?: string, // Default to demo token
26
+ maxRetries?: number, initialBackoff?: number);
27
+ private handleResponse;
28
+ private getHeaders;
29
+ createStep(options: CreateStepOptions): Promise<string>;
30
+ resolveNextInstruction(stepId: string, instruction: Instruction | null): Promise<any>;
31
+ uploadScreenshot(screenshot_bytes: Buffer): Promise<string>;
32
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,GAAG,CAAC;IACV,MAAM,EAAE,GAAG,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,sBAAsB,EAAE,MAAM,CAAA;IAC9B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,qBAAa,QAAS,SAAQ,KAAK;IAExB,MAAM,EAAE,MAAM;IAEd,IAAI,CAAC,EAAE,GAAG;gBAFV,MAAM,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACR,IAAI,CAAC,EAAE,GAAG,YAAA;IASnB,QAAQ;CAGT;AAED,qBAAa,SAAS;IAElB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,cAAc;gBAHd,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAmD,EAAG,wBAAwB;IACrF,UAAU,GAAE,MAAU,EACtB,cAAc,GAAE,MAAa;YAGzB,cAAc;IA+B5B,OAAO,CAAC,UAAU;YAWJ,SAAS;IAyBjB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBvD,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,IAAI;IAatE,gBAAgB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAclE"}