@milldr/crono 0.1.0 → 0.2.0
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 +128 -0
- package/dist/commands/add.d.ts +9 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +65 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/export.d.ts +8 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +142 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/log.d.ts +6 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +40 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/cronometer/auth.d.ts +31 -0
- package/dist/cronometer/auth.d.ts.map +1 -0
- package/dist/cronometer/auth.js +151 -0
- package/dist/cronometer/auth.js.map +1 -0
- package/dist/cronometer/export.d.ts +22 -0
- package/dist/cronometer/export.d.ts.map +1 -0
- package/dist/cronometer/export.js +83 -0
- package/dist/cronometer/export.js.map +1 -0
- package/dist/cronometer/parse.d.ts +35 -0
- package/dist/cronometer/parse.d.ts.map +1 -0
- package/dist/cronometer/parse.js +158 -0
- package/dist/cronometer/parse.js.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -1
- package/dist/kernel/add-custom-food.d.ts +22 -0
- package/dist/kernel/add-custom-food.d.ts.map +1 -0
- package/dist/kernel/add-custom-food.js +314 -0
- package/dist/kernel/add-custom-food.js.map +1 -0
- package/dist/kernel/client.d.ts +15 -0
- package/dist/kernel/client.d.ts.map +1 -1
- package/dist/kernel/client.js +92 -1
- package/dist/kernel/client.js.map +1 -1
- package/dist/kernel/log-food.d.ts +17 -0
- package/dist/kernel/log-food.d.ts.map +1 -0
- package/dist/kernel/log-food.js +230 -0
- package/dist/kernel/log-food.js.map +1 -0
- package/dist/kernel/login.d.ts.map +1 -1
- package/dist/kernel/login.js +24 -1
- package/dist/kernel/login.js.map +1 -1
- package/package.json +5 -1
- package/dist/debug-nav.d.ts +0 -2
- package/dist/debug-nav.d.ts.map +0 -1
- package/dist/debug-nav.js +0 -99
- package/dist/debug-nav.js.map +0 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright code generator for Cronometer food logging.
|
|
3
|
+
*
|
|
4
|
+
* Returns a code string that executes remotely via
|
|
5
|
+
* kernel.browsers.playwright.execute(). The code has access to
|
|
6
|
+
* `page`, `context`, and `browser` from the Playwright environment.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Generate Playwright code for logging a food to the Cronometer diary.
|
|
10
|
+
*
|
|
11
|
+
* Flow:
|
|
12
|
+
* navigate to #diary → right-click meal → "Add Food" → search food name →
|
|
13
|
+
* select result → set servings → "Add to Diary"
|
|
14
|
+
*/
|
|
15
|
+
export function buildLogFoodCode(entry) {
|
|
16
|
+
const { name, meal, servings } = entry;
|
|
17
|
+
const mealLabel = meal
|
|
18
|
+
? meal.charAt(0).toUpperCase() + meal.slice(1).toLowerCase()
|
|
19
|
+
: "Uncategorized";
|
|
20
|
+
const foodName = JSON.stringify(name);
|
|
21
|
+
const servingCount = servings ?? 1;
|
|
22
|
+
return `
|
|
23
|
+
const foodName = ${foodName};
|
|
24
|
+
const mealLabel = ${JSON.stringify(mealLabel)};
|
|
25
|
+
const servingCount = ${servingCount};
|
|
26
|
+
|
|
27
|
+
// Navigate to diary
|
|
28
|
+
await page.goto('https://cronometer.com/#diary', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
29
|
+
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
30
|
+
|
|
31
|
+
// Verify we're logged in
|
|
32
|
+
const url = page.url();
|
|
33
|
+
if (url.includes('/login') || url.includes('/signin')) {
|
|
34
|
+
return { success: false, error: 'Not logged in. Login may have failed.' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper: find and click an element from a list of selectors
|
|
38
|
+
async function clickFirst(selectors, description) {
|
|
39
|
+
for (const sel of selectors) {
|
|
40
|
+
try {
|
|
41
|
+
const el = page.locator(sel);
|
|
42
|
+
if (await el.count() > 0) {
|
|
43
|
+
await el.first().click();
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Helper: right-click an element from a list of selectors
|
|
52
|
+
async function rightClickFirst(selectors, description) {
|
|
53
|
+
for (const sel of selectors) {
|
|
54
|
+
try {
|
|
55
|
+
const el = page.locator(sel);
|
|
56
|
+
if (await el.count() > 0) {
|
|
57
|
+
await el.first().click({ button: 'right' });
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Right-click the meal category
|
|
66
|
+
const clicked = await rightClickFirst([
|
|
67
|
+
'text="' + mealLabel + '"',
|
|
68
|
+
':has-text("' + mealLabel + '")',
|
|
69
|
+
], 'meal category');
|
|
70
|
+
if (!clicked) {
|
|
71
|
+
return { success: false, error: 'Could not find meal category "' + mealLabel + '" in diary' };
|
|
72
|
+
}
|
|
73
|
+
await page.waitForSelector('text="Add Food..."', { timeout: 3000 }).catch(() =>
|
|
74
|
+
page.waitForSelector('text="Add Food"', { timeout: 2000 }).catch(() => {})
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Click "Add Food..." in context menu
|
|
78
|
+
const addFoodClicked = await clickFirst([
|
|
79
|
+
'text="Add Food..."',
|
|
80
|
+
'text="Add Food\u2026"',
|
|
81
|
+
'text="Add Food"',
|
|
82
|
+
'[role="menuitem"]:has-text("Add Food")',
|
|
83
|
+
], 'Add Food menu item');
|
|
84
|
+
if (!addFoodClicked) {
|
|
85
|
+
return { success: false, error: 'Could not find "Add Food" in context menu' };
|
|
86
|
+
}
|
|
87
|
+
await page.waitForTimeout(200);
|
|
88
|
+
|
|
89
|
+
// Wait for "Add Food to Diary" dialog
|
|
90
|
+
try {
|
|
91
|
+
await page.waitForSelector('text="Add Food to Diary"', { timeout: 5000 });
|
|
92
|
+
} catch {
|
|
93
|
+
return { success: false, error: 'Add Food to Diary dialog did not appear' };
|
|
94
|
+
}
|
|
95
|
+
await page.waitForTimeout(300);
|
|
96
|
+
|
|
97
|
+
// Search for the food
|
|
98
|
+
const searchSelectors = [
|
|
99
|
+
'input[placeholder*="Search all foods" i]',
|
|
100
|
+
'input[placeholder*="Search" i]',
|
|
101
|
+
'input[placeholder*="food" i]',
|
|
102
|
+
'input.gwt-TextBox',
|
|
103
|
+
'input[type="text"]',
|
|
104
|
+
'input[type="search"]',
|
|
105
|
+
];
|
|
106
|
+
let searched = false;
|
|
107
|
+
for (const sel of searchSelectors) {
|
|
108
|
+
try {
|
|
109
|
+
const el = page.locator(sel);
|
|
110
|
+
if (await el.count() > 0) {
|
|
111
|
+
await el.first().click();
|
|
112
|
+
await page.waitForTimeout(200);
|
|
113
|
+
await el.first().fill('');
|
|
114
|
+
await page.keyboard.type(foodName, { delay: 50 });
|
|
115
|
+
searched = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
} catch {}
|
|
119
|
+
}
|
|
120
|
+
if (!searched) {
|
|
121
|
+
return { success: false, error: 'Could not find food search bar in Add Food dialog' };
|
|
122
|
+
}
|
|
123
|
+
await page.waitForTimeout(300);
|
|
124
|
+
|
|
125
|
+
// Click SEARCH
|
|
126
|
+
await clickFirst([
|
|
127
|
+
'text="SEARCH"',
|
|
128
|
+
'button:has-text("SEARCH")',
|
|
129
|
+
'button:has-text("Search")',
|
|
130
|
+
], 'SEARCH button');
|
|
131
|
+
|
|
132
|
+
// Wait for results
|
|
133
|
+
try {
|
|
134
|
+
await page.waitForSelector('td:has-text("' + foodName + '")', { timeout: 8000 });
|
|
135
|
+
} catch {
|
|
136
|
+
return { success: false, error: 'No food found matching "' + foodName + '"' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Select the search result
|
|
140
|
+
const resultSelectors = [
|
|
141
|
+
'td:has-text("' + foodName + '")',
|
|
142
|
+
'tr:has-text("' + foodName + '") td',
|
|
143
|
+
'.gwt-HTML:has-text("' + foodName + '")',
|
|
144
|
+
'div:has-text("' + foodName + '"):not(:has(input))',
|
|
145
|
+
];
|
|
146
|
+
let resultClicked = false;
|
|
147
|
+
for (const sel of resultSelectors) {
|
|
148
|
+
try {
|
|
149
|
+
const el = page.locator(sel);
|
|
150
|
+
if (await el.count() > 0) {
|
|
151
|
+
await el.first().click();
|
|
152
|
+
resultClicked = true;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
157
|
+
if (!resultClicked) {
|
|
158
|
+
return { success: false, error: 'No food found matching "' + foodName + '"' };
|
|
159
|
+
}
|
|
160
|
+
await page.waitForTimeout(200);
|
|
161
|
+
|
|
162
|
+
// Wait for the detail panel with serving size
|
|
163
|
+
try {
|
|
164
|
+
await page.waitForSelector('text="Serving Size"', { timeout: 5000 });
|
|
165
|
+
} catch {
|
|
166
|
+
return { success: false, error: 'Serving Size panel did not appear for "' + foodName + '"' };
|
|
167
|
+
}
|
|
168
|
+
await page.waitForTimeout(500);
|
|
169
|
+
|
|
170
|
+
// If servings != 1, update the serving size input
|
|
171
|
+
if (servingCount !== 1) {
|
|
172
|
+
let servingFilled = false;
|
|
173
|
+
try {
|
|
174
|
+
servingFilled = await page.evaluate((count) => {
|
|
175
|
+
const walker = document.createTreeWalker(
|
|
176
|
+
document.body,
|
|
177
|
+
NodeFilter.SHOW_TEXT,
|
|
178
|
+
{ acceptNode: (node) =>
|
|
179
|
+
node.textContent && node.textContent.trim() === 'Serving Size'
|
|
180
|
+
? NodeFilter.FILTER_ACCEPT
|
|
181
|
+
: NodeFilter.FILTER_REJECT
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
const textNode = walker.nextNode();
|
|
185
|
+
if (!textNode) return false;
|
|
186
|
+
|
|
187
|
+
let container = textNode.parentElement;
|
|
188
|
+
for (let i = 0; i < 5 && container; i++) {
|
|
189
|
+
const input = container.querySelector('input');
|
|
190
|
+
if (input) {
|
|
191
|
+
input.focus();
|
|
192
|
+
input.select();
|
|
193
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(
|
|
194
|
+
window.HTMLInputElement.prototype, 'value'
|
|
195
|
+
).set;
|
|
196
|
+
nativeSetter.call(input, String(count));
|
|
197
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
198
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
container = container.parentElement;
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}, servingCount);
|
|
205
|
+
} catch {}
|
|
206
|
+
|
|
207
|
+
if (!servingFilled) {
|
|
208
|
+
return { success: false, error: 'Could not update serving size for "' + foodName + '"' };
|
|
209
|
+
}
|
|
210
|
+
await page.waitForTimeout(500);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Click "ADD TO DIARY"
|
|
214
|
+
const addClicked = await clickFirst([
|
|
215
|
+
'button:has-text("ADD TO DIARY")',
|
|
216
|
+
'button:has-text("Add to Diary")',
|
|
217
|
+
'text="ADD TO DIARY"',
|
|
218
|
+
'text="Add to Diary"',
|
|
219
|
+
'button[type="submit"]',
|
|
220
|
+
], 'ADD TO DIARY button');
|
|
221
|
+
if (!addClicked) {
|
|
222
|
+
return { success: false, error: 'Could not find "Add to Diary" button' };
|
|
223
|
+
}
|
|
224
|
+
await page.waitForSelector('text="Add Food to Diary"', { state: 'hidden', timeout: 8000 }).catch(() => {});
|
|
225
|
+
await page.waitForTimeout(300);
|
|
226
|
+
|
|
227
|
+
return { success: true };
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=log-food.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-food.js","sourceRoot":"","sources":["../../src/kernel/log-food.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAmB;IAClD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEvC,MAAM,SAAS,GAAG,IAAI;QACpB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC5D,CAAC,CAAC,eAAe,CAAC;IAEpB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,QAAQ,IAAI,CAAC,CAAC;IAEnC,OAAO;uBACc,QAAQ;wBACP,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;2BACtB,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2MpC,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/kernel/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAQ5C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAKjD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/kernel/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAQ5C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAKjD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgH7E"}
|
package/dist/kernel/login.js
CHANGED
|
@@ -119,7 +119,30 @@ export function buildAutoLoginCode(username, password) {
|
|
|
119
119
|
|
|
120
120
|
const url = page.url();
|
|
121
121
|
const loggedIn = !url.includes('/login') && !url.includes('/signin');
|
|
122
|
-
|
|
122
|
+
|
|
123
|
+
// If still on login page, check for error messages (rate limit, wrong creds, etc.)
|
|
124
|
+
let loginError = null;
|
|
125
|
+
if (!loggedIn) {
|
|
126
|
+
loginError = await page.evaluate(() => {
|
|
127
|
+
const selectors = [
|
|
128
|
+
'.error-message', '.alert', '.notification',
|
|
129
|
+
'[class*="error"]', '[class*="alert"]',
|
|
130
|
+
'.gwt-HTML',
|
|
131
|
+
];
|
|
132
|
+
for (const sel of selectors) {
|
|
133
|
+
const els = document.querySelectorAll(sel);
|
|
134
|
+
for (const el of els) {
|
|
135
|
+
const text = el.textContent?.trim();
|
|
136
|
+
if (text && text.length > 5 && text.length < 300 && el.offsetParent !== null) {
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { success: true, loggedIn, url, loginError };
|
|
123
146
|
`;
|
|
124
147
|
}
|
|
125
148
|
//# sourceMappingURL=login.js.map
|
package/dist/kernel/login.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/kernel/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;;;;;;GAMN,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;GAGN,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,QAAgB;IACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAE1C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAiCyB,QAAQ;;;;;;;;;;;;;;;;;kCAiBR,QAAQ
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/kernel/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;;;;;;GAMN,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;GAGN,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,QAAgB;IACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAE1C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAiCyB,QAAQ;;;;;;;;;;;;;;;;;kCAiBR,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDvC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milldr/crono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI for Cronometer automation via Kernel.sh",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"automation",
|
|
27
27
|
"kernel"
|
|
28
28
|
],
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/milldr/crono"
|
|
32
|
+
},
|
|
29
33
|
"author": "Daniel Miller",
|
|
30
34
|
"license": "MIT",
|
|
31
35
|
"engines": {
|
package/dist/debug-nav.d.ts
DELETED
package/dist/debug-nav.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-nav.d.ts","sourceRoot":"","sources":["../src/debug-nav.ts"],"names":[],"mappings":""}
|
package/dist/debug-nav.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debug script: dump date navigation elements from Cronometer diary.
|
|
3
|
-
* Run with: npx tsx src/debug-nav.ts
|
|
4
|
-
*/
|
|
5
|
-
import Kernel from "@onkernel/sdk";
|
|
6
|
-
import { getCredential } from "./credentials.js";
|
|
7
|
-
import { buildAutoLoginCode } from "./kernel/login.js";
|
|
8
|
-
async function main() {
|
|
9
|
-
const apiKey = process.env["KERNEL_API_KEY"] ?? getCredential("kernel-api-key");
|
|
10
|
-
process.env["KERNEL_API_KEY"] = apiKey;
|
|
11
|
-
const kernel = new Kernel();
|
|
12
|
-
const browser = await kernel.browsers.create({
|
|
13
|
-
headless: true,
|
|
14
|
-
stealth: true,
|
|
15
|
-
timeout_seconds: 120,
|
|
16
|
-
});
|
|
17
|
-
try {
|
|
18
|
-
const username = getCredential("cronometer-username");
|
|
19
|
-
const password = getCredential("cronometer-password");
|
|
20
|
-
// Login
|
|
21
|
-
await kernel.browsers.playwright.execute(browser.session_id, {
|
|
22
|
-
code: buildAutoLoginCode(username, password),
|
|
23
|
-
timeout_sec: 60,
|
|
24
|
-
});
|
|
25
|
-
// Navigate to diary and dump nav info
|
|
26
|
-
const result = await kernel.browsers.playwright.execute(browser.session_id, {
|
|
27
|
-
code: `
|
|
28
|
-
await page.goto('https://cronometer.com/#diary', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
29
|
-
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
30
|
-
await page.waitForTimeout(3000);
|
|
31
|
-
|
|
32
|
-
const debug = await page.evaluate(() => {
|
|
33
|
-
// 1. All images and their src attributes
|
|
34
|
-
const imgs = Array.from(document.querySelectorAll('img')).map(img => ({
|
|
35
|
-
src: img.getAttribute('src'),
|
|
36
|
-
alt: img.getAttribute('alt'),
|
|
37
|
-
visible: img.offsetParent !== null,
|
|
38
|
-
width: img.offsetWidth,
|
|
39
|
-
height: img.offsetHeight,
|
|
40
|
-
classes: img.className,
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
// 2. All buttons/clickable elements near the top of the page
|
|
44
|
-
const buttons = Array.from(document.querySelectorAll('button, [role="button"], .gwt-PushButton')).map(el => ({
|
|
45
|
-
text: (el.textContent || '').trim().substring(0, 80),
|
|
46
|
-
classes: el.className,
|
|
47
|
-
visible: el.offsetParent !== null,
|
|
48
|
-
tag: el.tagName,
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
// 3. Elements that might be date-related
|
|
52
|
-
const dateEls = Array.from(document.querySelectorAll('[class*="date" i], [class*="calendar" i], [class*="nav" i], [class*="arrow" i], [class*="picker" i]')).map(el => ({
|
|
53
|
-
tag: el.tagName,
|
|
54
|
-
classes: el.className,
|
|
55
|
-
text: (el.textContent || '').trim().substring(0, 80),
|
|
56
|
-
visible: el.offsetParent !== null,
|
|
57
|
-
html: el.outerHTML.substring(0, 200),
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
// 4. Look for any element containing a date-like text (month name)
|
|
61
|
-
const dateTexts = [];
|
|
62
|
-
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
63
|
-
let node;
|
|
64
|
-
while (node = walker.nextNode()) {
|
|
65
|
-
const t = (node.textContent || '').trim();
|
|
66
|
-
if (/(?:January|February|March|April|May|June|July|August|September|October|November|December)/i.test(t)) {
|
|
67
|
-
dateTexts.push({
|
|
68
|
-
text: t.substring(0, 100),
|
|
69
|
-
parentTag: node.parentElement?.tagName,
|
|
70
|
-
parentClasses: node.parentElement?.className,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 5. GWT PushButton elements (Cronometer uses GWT)
|
|
76
|
-
const pushButtons = Array.from(document.querySelectorAll('.gwt-PushButton, .gwt-PushButton-up, .gwt-PushButton-down')).map(el => ({
|
|
77
|
-
classes: el.className,
|
|
78
|
-
html: el.outerHTML.substring(0, 300),
|
|
79
|
-
visible: el.offsetParent !== null,
|
|
80
|
-
}));
|
|
81
|
-
|
|
82
|
-
return { imgs, buttons, dateEls, dateTexts, pushButtons };
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return debug;
|
|
86
|
-
`,
|
|
87
|
-
timeout_sec: 60,
|
|
88
|
-
});
|
|
89
|
-
console.log(JSON.stringify(result.result, null, 2));
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
92
|
-
try {
|
|
93
|
-
await kernel.browsers.deleteByID(browser.session_id);
|
|
94
|
-
}
|
|
95
|
-
catch { }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
main().catch(console.error);
|
|
99
|
-
//# sourceMappingURL=debug-nav.js.map
|
package/dist/debug-nav.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-nav.js","sourceRoot":"","sources":["../src/debug-nav.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GACV,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,MAAO,CAAC;IAExC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,GAAG;KACrB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAE,CAAC;QAEvD,QAAQ;QACR,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE;YAC3D,IAAI,EAAE,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC;YAC5C,WAAW,EAAE,EAAE;SAChB,CAAC,CAAC;QAEH,sCAAsC;QACtC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CACrD,OAAO,CAAC,UAAU,EAClB;YACE,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA2DL;YACD,WAAW,EAAE,EAAE;SAChB,CACF,CAAC;QAEF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
|