continente-mcp 4.1.0 → 4.1.1

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.
@@ -100,33 +100,55 @@ export async function isLoggedIn(page) {
100
100
  return payload?.resources?.customerAuthenticated === true;
101
101
  }
102
102
 
103
- async function fillFirst(page, selectors, value, label) {
104
- for (const selector of selectors) {
105
- const input = await page.waitForSelector(selector, { timeout: 3000 }).catch(() => null);
106
- if (input) {
107
- await input.fill(value);
108
- return;
109
- }
103
+ const EMAIL_INPUTS = '#userNameCC:visible, input[name="userNameCC"]:visible, input[type="email"]:visible, input[name="email"]:visible, input[name="username"]:visible, input[type="text"]:visible';
104
+ const PASSWORD_INPUTS = '#password_input:visible, input[name="password_input"]:visible, input[type="password"]:visible, input[name="password"]:visible';
105
+ const VERIFICATION_INPUT = 'input[id^="altcha_checkbox_"]:visible';
106
+
107
+ async function submitLoginStep(frame) {
108
+ await frame.getByRole('button', { name: /^(Avançar|Continuar|Entrar|Login)$/i })
109
+ .click({ timeout: 15000 });
110
+ }
111
+
112
+ export async function completeLoginForm(frame, { email, password, log: logger = log }) {
113
+ logger('Entering email.');
114
+ await frame.locator(EMAIL_INPUTS).first().fill(email, { timeout: 15000 });
115
+ await submitLoginStep(frame);
116
+
117
+ // Continente may ask for its native verification before showing the password.
118
+ // Wait for either stage: the verification widget is inserted asynchronously.
119
+ await frame.locator(`${PASSWORD_INPUTS}, ${VERIFICATION_INPUT}`).first()
120
+ .waitFor({ state: 'visible', timeout: 15000 });
121
+ const verification = frame.locator(VERIFICATION_INPUT);
122
+ if (await verification.count()) {
123
+ logger('Completing login verification.');
124
+ await verification.click({ timeout: 10000 });
125
+ await frame.getByText(/^(Verificado|Verified)$/i)
126
+ .waitFor({ state: 'visible', timeout: 60000 });
127
+ await submitLoginStep(frame);
110
128
  }
111
- throw new Error(`Could not find ${label} input`);
129
+
130
+ logger('Entering password.');
131
+ await frame.locator(PASSWORD_INPUTS).first().fill(password, { timeout: 15000 });
132
+ await submitLoginStep(frame);
112
133
  }
113
134
 
114
- async function clickFirst(page, selectors) {
115
- for (const selector of selectors) {
116
- const button = await page.$(selector);
117
- if (button) {
118
- await button.click();
119
- return true;
135
+ export async function acceptCookies(page) {
136
+ const button = page.getByRole('button', { name: /PERMITIR TODOS|Permitir todos/i });
137
+ if (!await button.isVisible()) {
138
+ await button.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {});
139
+ if (!await button.isVisible()) return;
140
+ }
141
+ // The banner can render before its click handler is ready. Confirm dismissal
142
+ // before interacting with the iframe, which the banner otherwise covers.
143
+ for (let attempt = 0; attempt < 3; attempt++) {
144
+ await button.click({ timeout: 5000 });
145
+ try {
146
+ await button.waitFor({ state: 'hidden', timeout: 5000 });
147
+ return;
148
+ } catch (error) {
149
+ if (attempt === 2) throw new Error('Could not dismiss the Continente cookie banner');
120
150
  }
121
151
  }
122
- await page.keyboard.press('Enter');
123
- return false;
124
- }
125
-
126
- async function acceptCookies(page) {
127
- await page.getByRole('button', { name: /PERMITIR TODOS|Permitir todos/i })
128
- .click({ timeout: 5000 })
129
- .catch(() => {});
130
152
  }
131
153
 
132
154
  async function findLoginFrame(page) {
@@ -172,37 +194,7 @@ export async function autoLogin({
172
194
  await acceptCookies(page);
173
195
  const loginFrame = await findLoginFrame(page);
174
196
 
175
- logger('Entering email.');
176
- await fillFirst(loginFrame, [
177
- '#userNameCC',
178
- 'input[name="userNameCC"]',
179
- 'input[type="email"]',
180
- 'input[name="email"]',
181
- 'input[name="username"]',
182
- 'input[type="text"]',
183
- ], email, 'email');
184
-
185
- await clickFirst(loginFrame, [
186
- 'button[type="submit"]',
187
- 'button:has-text("Avançar")',
188
- 'button:has-text("Continuar")',
189
- ]);
190
-
191
- logger('Entering password.');
192
- await page.waitForTimeout(1500);
193
- await fillFirst(loginFrame, [
194
- '#password_input',
195
- 'input[name="password_input"]',
196
- 'input[type="password"]',
197
- 'input[name="password"]',
198
- ], password, 'password');
199
-
200
- await clickFirst(loginFrame, [
201
- 'button[type="submit"]',
202
- 'button:has-text("Avançar")',
203
- 'button:has-text("Entrar")',
204
- 'button:has-text("Login")',
205
- ]);
197
+ await completeLoginForm(loginFrame, { email, password, log: logger });
206
198
 
207
199
  await page.waitForTimeout(4000);
208
200
  await page.waitForLoadState('domcontentloaded', { timeout: 15000 }).catch(() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "continente-mcp",
3
- "version": "4.1.0",
3
+ "version": "4.1.1",
4
4
  "description": "MCP server for Continente.pt — search products, manage your cart, and browse order history.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -89,7 +89,7 @@ async function ensureBrowser() {
89
89
  return browser;
90
90
  }
91
91
 
92
- async function goto(url, wait = 'networkidle') {
92
+ async function goto(url, wait = 'domcontentloaded') {
93
93
  await ensureBrowser();
94
94
  const response = await page.goto(url, { waitUntil: wait, timeout: 20000 });
95
95
  if (!response?.ok()) throw new Error(`Continente page returned HTTP ${response?.status() ?? 'unknown'}`);
@@ -455,7 +455,7 @@ async function getOrderHistory(limit = 5) {
455
455
  }
456
456
 
457
457
  async function getOrderProducts(orderDetailUrl) {
458
- const response = await page.goto(orderDetailUrl, { waitUntil: 'networkidle', timeout: 20000 });
458
+ const response = await page.goto(orderDetailUrl, { waitUntil: 'domcontentloaded', timeout: 20000 });
459
459
  if (!response?.ok() || page.url().includes('/login')) throw new Error('order_detail_unavailable');
460
460
 
461
461
  const products = await page.evaluate(() => {