- name
- noon-listing
- description
- Noon listing operations — create SKU (3-step wizard) and edit listings (Offer / Content / Sizes / Groups tabs). Load when creating, editing, pricing, restocking, or updating content on a noon SKU.
- requires
- ["noon-shared"]
- review
- {"criteria":"- The SKU is ACTUALLY created and live on noon, not just \"submitted\":\n the create flow reached the success redirect to\n /catalog/{noon_sku}/p, and the Offer tab shows the base price /\n sale price / stock / barcode as entered (committed on the live\n page). Content/Sizes match the request.\n- For a NIS import: the import's **Report File** is non-empty and\n every row carries a `catalog_sku`. `Status: Complete` and\n `Total Processed Rows: N` are NOT evidence of creation — an import\n that created nothing shows both, with a 4-byte Report File.\n- Every bilingual field the request supplied in English also has its\n Arabic half on the live Content tab (title, long description, each\n feature bullet) — not a stub a fraction of the English length.\n- An edit is done only when the live page reflects it (green success\n + updated values), not on a toast alone.\n","verify_by":"Open the created SKU's catalog page and its Offer tab; confirm price\n/ stock / barcode match what was entered. For an edit, reload the tab\nand confirm the new values persisted. A feed/toast without the live\npage reflecting it is a gap. For a NIS import, additionally download\nthe import's Report File and confirm one row per SKU with a\n`catalog_sku`, and open the Content tab to confirm the Arabic half of\nevery filled English field. Import-row counters are not evidence.\n"}
# Noon — Listing Operations
> **PREREQUISITE:** Read `../noon-shared/SKILL.md` for login, page
> structure, modals, and button-click patterns.
Covers SKU creation and the post-creation edit flow (price, stock,
barcode, content, visibility status).
> **Two create paths — PREFER file-based, FALL BACK to click.**
> File-based (§1, NIS spreadsheet import) sets every hard field through a
> spreadsheet, so it never touches the Ant-Design dropdowns (Warranty,
> Department, Gender, Size, Content selects) that the click wizard needs
> a *trusted mouse* to open — those dropdowns are the recurring wall on
> the click path. Use file-based whenever creating one or more SKUs of a
> known category. Use the click wizard (§2) only for a one-off where you
> can drive a real trusted click, or to *edit* a SKU after creation.
## 1. Create Listing — File-based (PREFERRED, NIS import)
Noon's **NIS** (noon Item Sheet) importer creates SKUs in bulk from a
spreadsheet. Everything the click wizard sets via a dropdown is a plain
cell here, so it sidesteps the anti-automation selects entirely.
**Flow:** `Imports` → **Add Import** → **Type = Content**, **Subtype =
NIS Create/Update** → pick the category → **Download** the template →
fill it → **Next** → upload → SKUs go to async Quality Check (QC).
URL: `https://noon-catalog.noon.partners/en/imports/create?project=PRJ{project_id}`
The Type / Subtype selects are Ant-Design dropdowns — open each with a
trusted `click_at_xy` on the field, then option-click the item in
`.ant-select-dropdown .ant-select-item` (type-to-filter does NOT work;
see `../noon-shared`).
### 1.1 Which import for which field
Imports are keyed by **Type → Subtype**. The relevant ones:
| Type | Subtype | Creates / sets |
|------|---------|----------------|
| Content | **NIS Create/Update** | **Creates new SKUs** — identity, sizes, attributes, images, in bulk. This is the create step. |
| Content | Product Import | *Updates* content (title/description/attributes) of **existing** SKUs only. |
| Pricing | **Price Update** | Base price + sale price/window for existing SKUs (see §1.3). |
| Pricing | Price Range Update | Long-dated sale windows (the discount pattern, §1.3). |
| Stock | (stock subtype) | On-hand quantity for existing SKUs. **Match the store's warehouse type**: an FBP Stock Update fails ("no FBP warehouse") if the store has none; FBN stock requires an ASN shipment flow, not a stock import. Check the SKU's Offer→Stock section for which warehouse the store actually has before choosing the stock import. |
| Warranty | (warranty subtype) | Warranty type for existing SKUs (file equivalent of the click "No Warranty"). |
So a full file-based create is: **NIS Create/Update** (identity + images)
→ then **Price Update** (price) and **Stock** as follow-up imports keyed
on the `seller_sku`/`partner_sku` you assigned. Each import's own
"ABOUT THIS UPLOAD" panel lists its exact Required/Optional columns —
read it in-page before filling; do not assume.
### 1.2 The NIS template (Content → NIS Create/Update)
- **Category**: "Download templates for" → **Specific category** →
drill the tree (e.g. **Apparel** → product-types incl. *Socks &
Tights*) and select it, **plus** the target store/marketplace, to
enable the per-category **Download English** / **Download English +
Arabic** buttons (AE stores need the +Arabic template for the local
title). "All categories" downloads a generic shell without the
category attribute columns — only use it to see the structure.
- **`With Instructions`** checkbox adds a column-guidance row — leave it
on the first time.
- **Core required columns** (from the template's `valid values` sheet):
`family`, `product_type`, `product_subtype`, `seller_sku`,
`item_condition` (`New`), `parent_child_variation` (`Parent`/`Child`
for sized products), and per-marketplace `vat_rate_ae` / `vat_rate_sa`
/ `vat_rate_eg` (`Std`). Category attribute columns + image-URL columns
follow — fill per the in-sheet guidance and the linked "How to fill out
the NIS sheet" article.
- Each row needs a **unique `seller_sku`**. Partial failures are
per-row: good rows still create; fix the error file and re-upload the
rest.
- **A parent needs a child — a lone parent creates NOTHING.** Verified
live: a single row with `parent_child_variation=parent` imports
"successfully" but the SKU never appears, and the error file's
`creation_error` column reads **`child is missing`**. Every product
needs **two rows sharing one `parent_group_key`**: a `parent` row
(identity) **and** at least one `child` row (the sellable variant,
with its own distinct `seller_sku` and a `size_variation`/`size_map`,
e.g. `One Size`). The **child** is what carries the offer — follow-up
Pricing/Stock imports key on the **child** `seller_sku`.
- **Multiple sizes = one parent row + one child row PER SIZE** (all
sharing the parent's `parent_group_key`). Verified live: a parent +
three children (`size_variation`/`size_map` = `S`, `M`, `L`, distinct
`seller_sku`s) created one parent noon-SKU with children `-1/-2/-3`,
one per size — the same shape a real sized product uses (e.g. an
S/M/L/XL/XXL family is a parent + five child rows). `size_map` is the
**noon** size and may differ from your label (e.g. `XXL` → noon `2XL`);
check the template's `valid values` / the live **Sizes tab** for the
noon size names. This is noon's **new** variation style — always use it
for new listings.
⚠️ **NIS has no Arabic-size column — only `size_variation` (Seller Size
EN) and `size_map` (noon Size).** So after a NIS create the sized
product's **Seller Size (AR)** — a `*`-mandatory field on the Sizes tab
— is not authored by you: noon back-fills it with the *English*
`size_variation` string, so the field looks satisfied while showing
English text in the Arabic column. (An earlier revision of this skill
said it shows `--`; re-verified, it is the copied English value.)
Either way the family is bound but not content-complete.
Fill the Arabic seller size per row on the live **Sizes tab** (edit each
size) after import. Verified live: a parent + S/M/L children imported
correctly (one parent hash, children `-1/-2/-3`, all shown bound on the
Sizes tab) but with **Seller Size (AR) holding the English
`size_variation` string** until replaced by hand.
- **Leave a field BLANK unless the task specified it.** This is the
default, not a preference. A NIS sheet has ~150 columns and almost all
of them are optional; filling one "because it is there" invents data
the seller never agreed to. Every invented value in the one audited
run was wrong: `hs_code` was a guessed 4-digit customs code (rejected
for length), a select attribute carried a value nobody claimed
(rejected as out-of-enum), and one answer of "100 AED" was fanned out
to `msrp_ae`, `msrp_sa` **and** `msrp_eg` — pricing two marketplaces
the user never priced, one of which the seller does not sell in.
> ⚠️ **On noon an invented value is effectively permanent.** A blank
> cell in a NIS *update* means "no change", NOT "clear" — verified: an
> update whose `msrp_*` cells were emptied imported with 0 errors and
> left the live values untouched. Clearing them on the Content tab did
> not persist either. So the only reliable moment to not have a wrong
> value is **before the first upload**.
If a field seems needed but the task did not give it, ASK — do not
infer it from the English copy, the other marketplaces, or the
Amazon listing.
- **Pre-flight the sheet BEFORE uploading — always:**
```bash
# Your CWD is the task workspace, not the skill dir — address the
# script through the synced skill path.
S=.claude/skills/noon-listing/scripts
# --markets is the marketplaces the TASK covers, not the ones the
# store has. "Only list on the UAE site" means --markets AE.
python3 $S/validate_nis.py FILLED.xlsx --markets AE
```
It fails the file on the traps that have each burned a live import: a
leftover template sample row, a select value that is not in the
template's `valid values` sheet, an out-of-range `hs_code`, an English
field whose Arabic half is empty, and a per-marketplace field
(`msrp_*`, `vat_rate_*`) set outside the task's scope. A live import costs minutes
and its error reporting is unreliable (below); this costs nothing.
- **DELETE the template's sample row(s) before uploading.** The
downloaded template ships a row carrying only `family` /
`product_type` / `product_subtype` and no `seller_sku`. Leave it in
and noon's Error File reports **only** that row's
`seller_sku is missing` and silently drops every real row's
`content_error`, while the counters read `1 error / N processed` —
which reads as success. Verified: the same import's Error File still
showed only the sample row 6.5 h later, and its Report File was empty.
- **Upload flow (filled NIS sheet):** Imports → Add Import → Type=Content,
Subtype=NIS Create/Update → **Next** → drag/drop or pick the `.xlsx`
(the hidden `input[type=file]` accepts `.csv,.xlsx`) → **Submit**. The
import runs async (a `IMP…` code); it shows Completed even on row
failures, so verify the SKUs actually appear in My Catalog.
- **`Total Processed Rows` is NOT "created" — the Report File is.**
The counter counts rows that passed *partner-level* validation; rows
that then fail content validation still count toward it. The only
trustworthy signal is the import's **Report File**: one row per SKU
with a filled `catalog_sku` (and `sku_parent` for a variation family).
A failed import's Report File is **4 bytes** — a UTF-8 BOM and a
newline. Download it every time; do not report success without it.
- **Reading the error file is the debug loop.** An import that shows
Completed but creates nothing failed row validation; open the import's
Result/error CSV — its trailing `partner_error` / `content_error` /
`creation_error` columns name the exact problem. Fix those cells
(use the exact valid-value strings) and re-upload. If the error file
names only a row you did not write, you still have a sample row in the
file (above) — remove it and re-upload to see the real errors.
- **Fill BOTH halves of every bilingual pair.** The `English + Arabic`
template pairs each localised field (`product_title_*`,
`long_description_*`, `feature_bullet_N_*`, `colour_name_*`). Filling
only `_en` imports clean — no error, no warning — and ships a
half-translated listing; shipped once with all four Arabic feature
bullets empty and Arabic title/description stubs a third the length of
their English source. `validate_nis.py` fails the file on a missing
`_ar`, and warns when one is suspiciously short.
### 1.3 Pricing import — optional high-base + long sale (a seller pattern)
Some sellers list a **high base price** and a **long-dated half-price
sale** so the page shows a large discount on day one, while the true
selling price is the sale price every day. This is a **guideline, not a
requirement** — only apply it when the seller asks for it.
To do it file-based, after the SKU exists run **Pricing → Price Update**
(columns: required `country_code`, `id_partner`, `partner_sku`; optional
`price`, `sale_price`, `sale_start`, `sale_end`, `is_active`):
- `price` = the high base (e.g. `100`)
- `sale_price` = the real everyday price (e.g. `50`)
- `sale_start` = today, `sale_end` = a far-future date (e.g. +5 years)
For a rolling window use **Pricing → Price Range Update**. If the seller
did not ask for the discount pattern, just set `price` to the real price
and leave the sale columns blank.
## 2. Create Listing — Click wizard (FALLBACK)
**URL**: `https://noon-catalog.noon.partners/en/catalog/create?project=PRJ{project_id}`
> Use this only when file-based isn't practical (a true one-off you can
> drive with a real trusted click). It needs trusted `click_at_xy` for
> every Ant-Design select; a programmatic `.click()` / nativeSetter does
> NOT register. If a required dropdown won't open, fall back to §1.
3-step wizard: **Category → Brand → Identity**.
> **MINIMAL PATH to a valid listing — do exactly these, in order, and
> STOP:**
> 1. Wizard create (Category → Brand → Identity), `fill_input` the SKU.
> 2. **Offer tab** → `fill_input` **Base Price**, then **set Warranty**
> (MANDATORY — see below), then **Save Changes** (green modal; price
> persists across reload).
> 3. **Content** on `/d?code=…&tab=content` → Product **Title** (EN via
> `fill_input`, Arabic via `Input.insertText` — see §2.7) + set
> **Gender** (MANDATORY; Department + Arabic gender auto-derive — §2.7)
> → **Save Changes** → Submit → "sent for review" (async QC — done;
> do NOT re-fill).
> 4. **Image** (mandatory ≥1): upload the seller's **real product photo**
> (§2.7) — a placeholder fails noon's async image validation and the
> listing never goes live.
> 5. **Seller Status → ON**.
>
> **Warranty is MANDATORY — but trivial: select "No Warranty".** The
> Offer save FAILS with `Save failed — Warranty / No Offer Created`
> unless the Warranty **type** is set. Do NOT try to configure a real
> warranty (service center + 1–60mo duration — that path IS an
> anti-automation rathole). Just open the Warranty **type** select and
> pick **"No Warranty"** (options: No Warranty / Seller Warranty /
> Manufacturer Warranty) — no service center or duration needed, and the
> offer saves. Open it with a trusted `click_at_xy` on the select's
> centre (it's below the fold — grow the viewport via
> `Emulation.setDeviceMetricsOverride` first), then click the
> **"No Warranty"** option in the `.ant-select-dropdown`. Verified live:
> price + No-Warranty → Save → offer created, persists across reload.
>
> **Gender IS MANDATORY (verified live) — Department derives from it.**
> Content Check flags `Missing Department` until Gender is set; you do
> NOT set Department directly (there is no Department field). Set the
> **English `Gender *`** select (e.g. `Men`) and noon **auto-fills the
> Arabic gender (`رجال`) and clears the Department requirement**. The
> Gender select is the same anti-automation ant-select as Warranty — open
> it with the virtual-list technique in §2.7 (grow viewport → scroll the
> dropdown's `.rc-virtual-list-holder` → in-panel trusted `click_at_xy`).
> An earlier note calling Gender "optional, skip it" was WRONG — a listing
> without Gender never clears mandatory content and never goes live.
>
> **These ARE optional — skip unless the seller asked:** Size Unit,
> Feature Bullets, Long Description, Material, Colour, and the other
> detailed-content attributes. The listing saves and goes live without
> them. If a seller explicitly wants one and its dropdown won't open with
> §2.7, note it as a manual follow-up rather than looping.
**Runnable Offer-tab snippet (price + No Warranty) — copy verbatim.** The
below-fold Warranty select can't be found by an un-scrolled DOM query;
this grows the viewport, trusted-clicks the Warranty card's select, and
picks "No Warranty". It also clears the price field first (`fill_input`
*appends* if a value is already present → `59.59.9`). Verified live:
```bash
browser-use <<'PY'
import time, json
# 1) price — clear first (fill_input appends to an existing value), then fill
js("var i=document.querySelector('input[name=new_price]'); if(i){i.value='';}")
fill_input('input[name="new_price"]', '59.90')
# 2) Warranty = No Warranty (MANDATORY). Grow viewport (it's below fold),
# trusted-click the Warranty card's ant-select, option-click No Warranty.
cdp("Emulation.setDeviceMetricsOverride", width=1500, height=2200, deviceScaleFactor=1, mobile=False)
time.sleep(2)
info = js("""(function(){
Ver no GitHub