Verify Multi-Country Product Catalog Coverage

Site ftglobalportfolios.comTask verify-multi-country-catalog-coverageVersion v2Updated Aug 12, 2026Category browser-automation

Compare First Trust Global Portfolios professional product catalogs across country-localized site paths using stable product-link and ISIN extraction, with optional diagnosis of SFDR fields on product detail pages. This skill was captured from a live agent session on ftglobalportfolios.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Enumerate and compare the professional product catalogs exposed by First Trust Global Portfolios for multiple country/localized paths. The catalog is represented by product links under .FundsTable__group; each product URL normally carries its ISIN in the isin_code query parameter.

Optionally inspect product detail pages to determine whether an SFDR classification is actually present in the rendered DOM, rather than treating a missing scraper field as a literal dash value.

Use Cases

Use when verifying whether the same product universe is available in several country sites, identifying country-specific additions or omissions, or comparing product counts and ISIN coverage. Supply country path codes such as {country-codes} and compare the returned ISIN sets across locales.

Use the optional detail-page diagnostic when a static scraper reports - for SFDR on every product and you need to distinguish an absent/hidden field from a real displayed dash or an incorrect selector.

Automation Flow

  1. For each requested country code, navigate directly to: https://www.ftglobalportfolios.com/{country-code}/professional/Products/
  2. If the site presents its consent/professional-investor gate, select the professional-investor option when shown, then select the visible Accept button. Wait briefly for the catalog to render.
  3. On the loaded catalog page, run this self-contained evaluator:
(() => {
  const links = [
    ...document.querySelectorAll('.FundsTable__group a[href*="/Products/"]'),
  ];
  const products = [
    ...new Map(
      links.map((a) => {
        const href = (() => {
          try {
            return new URL(a.href, location.href).href;
          } catch (_) {
            return a.href;
          }
        })();
        const url = (() => {
          try {
            return new URL(href);
          } catch (_) {
            return null;
          }
        })();
        const isin = url ? (url.searchParams.get("isin_code") || "").trim() : "";
        return [
          href,
          {
            name: (a.textContent || "").replace(/\s+/g, " ").trim(),
            url: href,
            isin: isin || null,
          },
        ];
      }),
    ).values(),
  ];
  const isins = [...new Set(products.map((p) => p.isin).filter(Boolean))];
  return {
    country: location.pathname.split("/").filter(Boolean)[0] || null,
    url: location.href,
    productCount: products.length,
    uniqueIsinCount: isins.length,
    products,
    isins,
    productsWithoutIsin: products
      .filter((p) => !p.isin)
      .map((p) => ({ name: p.name, url: p.url })),
  };
})();
  1. Aggregate one evaluator result per country. Compare isins as sets, reporting the union, intersection, and each country’s onlyInCountry and missingFromCountry values. Treat products without an isin_code as separately reviewable rather than silently discarding them.

  2. Optionally diagnose SFDR for any product URL obtained from step 3. Navigate directly to the catalog-provided URL, preserving its isin_code query parameter; do not guess a product slug or opaque identifier. After the consent gate is accepted and the detail page has rendered, run this evaluator:

(() => {
  const clean = (s) => (s || "").replace(/\s+/g, " ").trim();
  const sets = [...document.querySelectorAll(".Values__set")];
  const sfdrSets = sets.filter((set) =>
    /SFDR\s*Classification/i.test(set.textContent || ""),
  );
  const exactLabels = [...document.querySelectorAll("*")].filter(
    (el) =>
      el.children.length === 0 &&
      /^SFDR\s*Classification$/i.test(clean(el.textContent)),
  );
  const describe = (el) => {
    const parent = el.parentElement;
    const parentSet = el.closest(".Values__set");
    const sibling = el.nextElementSibling;
    const parentSibling = parent && parent.nextElementSibling;
    return {
      label: clean(el.textContent),
      valueFromNextSibling: clean(sibling && sibling.textContent),
      valueFromParentNextSibling: clean(parentSibling && parentSibling.textContent),
      containerText: clean((parentSet || parent || el).textContent),
      containerSelector: parentSet ? ".Values__set" : null,
    };
  };
  return {
    url: location.href,
    isin: new URL(location.href).searchParams.get("isin_code"),
    valuesSetCount: sets.length,
    sfdrContainerCount: sfdrSets.length,
    exactLabelCount: exactLabels.length,
    sfdrContainers: sfdrSets.map((set) => clean(set.textContent)),
    exactLabelMatches: exactLabels.map(describe),
    bodyContainsSfdr: /SFDR\s*Classification/i.test(document.body.innerText || ""),
  };
})();

Interpret the result as follows: bodyContainsSfdr: false means the rendered page does not expose the field to a DOM-only static scraper; bodyContainsSfdr: true with zero .Values__set matches indicates the scraper’s container selector is wrong; a matching container or exact label with an empty sibling value indicates a structure/value-extraction issue; and an explicitly returned - in the container text is a genuine displayed dash. Repeat on representative products/locales before changing the scraper globally.

Possible Friction Points

  • The localized country code is the first path segment, while the professional catalog path remains /professional/Products/; do not assume the Luxembourg path applies to every country.
  • A consent/investor modal can prevent the catalog from rendering. The button text is localized; the German flow exposed Professioneller Anleger before Accept, while other locales may expose a different professional-investor label.
  • Product rows can contain duplicate links, so deduplicate by absolute URL before counting. Use the isin_code query parameter for cross-country identity rather than product-link text or slug.
  • Wait for the catalog after dismissing the modal before extracting .FundsTable__group links; an early extraction can return an incomplete or empty table.
  • Product detail pages are reached with the catalog-provided product URL and its isin_code query parameter. Do not fabricate opaque product identifiers from names.
  • For SFDR diagnosis, search both the visible body text and .Values__set containers. A selector that assumes every field is a .Values__set entry can report dashes when the label is absent, rendered elsewhere, or not exposed in the static DOM.
  • The catalog selector is based on the observed FundsTable__group structure and the detail diagnostic uses observed .Values__set markup; both should be rechecked if the site redesigns its markup.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=ftglobalportfolios.com&task=verify-multi-country-catalog-coverage