diff --git a/domains/etsy.com/set_favorite.js b/domains/etsy.com/set_favorite.js index d7830573..27bddc73 100644 --- a/domains/etsy.com/set_favorite.js +++ b/domains/etsy.com/set_favorite.js @@ -84,7 +84,7 @@ if (initial.error) return lookupError(initial, false, null); if (initial.favorited === input.favorited) return { status: "ok", listing_id: id, previous_favorited: initial.favorited, favorited: initial.favorited, changed: false, mutation_attempted: false }; // The listing page's own button first; otherwise a result card's button for the same listing. - const buttons = [...document.querySelectorAll(`[data-ui="favorite-listing-button"][data-listing-id="${id}"]`)].filter(b => b.getClientRects().length); + const buttons = [...document.querySelectorAll(`[data-ui="favorite-listing-button"][data-listing-id="${id}"], button.listing-page-favorite-button[data-listing-id="${id}"]`)].filter(b => b.getClientRects().length); const button = buttons.find(b => !b.closest("[data-listing-card-v2], [data-palette-listing-id]")) || buttons[0]; if (!button) return fail("listing_not_on_page", `This page shows no favorite button for listing ${id}; open https://www.etsy.com/listing/${id} (or a search page that shows it) and call again. Nothing was changed.`, { previous_favorited: initial.favorited }); // An observer, because listing pages fill the resource-timing buffer and later entries never reach it. diff --git a/registry.json b/registry.json index 506ecc8a..cfafd736 100644 --- a/registry.json +++ b/registry.json @@ -1769,7 +1769,7 @@ "url_patterns_reason": "Narrowed to www.etsy.com: the marketplace (search, listings, cart, favorites) lives entirely there and etsy.com redirects to it; other subdomains (help center, forums) are unrelated products where the page tools' relative requests would fail.", "status": "draft", "last_verified": null, - "notes": "Signed-in only: records the initial favorite state, no-op if it already matches, otherwise presses the listing's favorite button on the current page once (listing page or a search page showing it) and confirms. Never run signed in (no Etsy account available); signed out returns not_logged_in and presses nothing." + "notes": "Signed-in only: records the initial favorite state, no-op if it already matches, otherwise presses the listing's favorite button on the current page once (listing page or a search page showing it) and confirms. The listing page's own heart is button.listing-page-favorite-button (no data-ui attribute), next to an Add to collection button with the same listing id that is never pressed. Author live check on 2026-10-27 (not independent QA), signed in on a listing page: false to true, a no-op repeat, then true to false, each read back with get_favorite_status and the account left as it started. Signed out returns not_logged_in and presses nothing." }, { "domain": "etsy.com", diff --git a/tests/etsy-tools.test.mjs b/tests/etsy-tools.test.mjs index adbbaf33..61798f22 100644 --- a/tests/etsy-tools.test.mjs +++ b/tests/etsy-tools.test.mjs @@ -639,7 +639,7 @@ test("get_favorite_status: reads Etsy's favorites map for each listing", async ( // Simulates Etsy's favorite buttons on a page: a press sends the favorites request and toggles the state. const favoriteButtons = (behaviour = "toggle") => (doc, path, site) => { - for (const button of doc.querySelectorAll('[data-ui="favorite-listing-button"]')) { + for (const button of doc.querySelectorAll('[data-ui="favorite-listing-button"], .listing-page-favorite-button, [data-add-to-collection-button]')) { button.addEventListener("click", function () { site.presses = (site.presses || 0) + 1; site.pressed = this; @@ -675,6 +675,7 @@ for (const [from, to] of [[false, true], [true, false]]) { const result = await h.run({ listing_id: "1000000003", favorited: to }); assert.deepEqual(result, { status: "ok", listing_id: "1000000003", previous_favorited: from, favorited: to, changed: true, mutation_attempted: true }); assert.equal(h.site.presses, 1); + assert.equal(h.site.pressed.getAttribute("data-testid"), "favorite-heart", "the heart, not Add to collection"); assert.deepEqual(h.calls.navigations, [], "page tools never navigate"); }); } @@ -704,7 +705,7 @@ test("set_favorite: presses the listing page's own button rather than a recommen const h = favoriteHarness({ signedIn: true, url: `${ORIGIN}/listing/1000000001` }); const result = await h.run({ listing_id: "1000000001", favorited: true }); assert.equal(result.changed, true); - assert.equal(h.site.pressed.hasAttribute("data-always-show"), true); + assert.equal(h.site.pressed.getAttribute("data-testid"), "favorite-heart"); }); test("set_favorite: a search result card's button is used when the listing page is not open", async () => { diff --git a/tests/fixtures/etsy/listing-classic.html b/tests/fixtures/etsy/listing-classic.html index 2abc633f..94c0d386 100644 --- a/tests/fixtures/etsy/listing-classic.html +++ b/tests/fixtures/etsy/listing-classic.html @@ -7,7 +7,8 @@

Fabricated Leather Pet Collar & Tag

NowPrice:$18.00+

Original Price: $30.00+

Loading
- + +
diff --git a/tests/fixtures/etsy/listing-plain.html b/tests/fixtures/etsy/listing-plain.html index 8ad95e47..2e45631f 100644 --- a/tests/fixtures/etsy/listing-plain.html +++ b/tests/fixtures/etsy/listing-plain.html @@ -5,6 +5,7 @@

Fabricated Blue Vase

$42.00

- + +