Use a Playwright action to read a switch, access point, firewall or printer's status, firmware and alerts from the vendor's cloud management portal
Check whether a site's switches, Wi-Fi access points, firewall or managed printers are online, which firmware they run and what alerts are open, by reading the vendor's cloud management portal. A Playwright workflow action signs in to the portal, opens the site, and returns each device's status, model, firmware and last-seen time, plus the site's open alerts. For MSPs and IT teams whose network or print devices are managed through a vendor's cloud dashboard.
| Runs in | Cloud sandbox (Playwright) |
| Uses | API key credential (portal username and password stored as custom headers) |
| Suggested approval | None (engineers and admins run it without approval), because it only reads |
| Category | Browser automation |
Test it with SuperIT before you rely on it
This is an example, not a ready-made action, and it hasn't been tested end to end. The portal address, page paths, labels and table columns are placeholders: adjust them for each portal. Before you use it, work through it with SuperIT: ask SuperIT to review the script against your environment and requirements, and adjust it together. Then test it, as in Test it. Test in the action editor isn't a dry run: it runs the script for real.
Why use an action instead of asking SuperIT
SuperIT has no web browser, and the SuperIT RMM agent runs on Windows devices, not on switches, access points, firewalls or printers. So SuperIT can't see those devices' status itself. A Playwright action signs in to the vendor's cloud portal headlessly in the cloud sandbox, with the portal login stored as an encrypted credential, and returns what the dashboard shows. For what a Windows device sees, such as its network settings, its printers and their queued jobs, just ask SuperIT in chat instead; it reads them from the device.
When to use it
- "Is the Wi-Fi at the Leeds office up? Users say it keeps dropping."
- "Which firmware are the switches at the main office on?"
- "Are there any open alerts on the Leeds firewall?"
- "Is the second-floor printer showing as online in the print portal?"
What the action does
- Opens a headless Chromium browser in the cloud sandbox.
- Signs in to the vendor's cloud portal with the username and password from the linked credential. If the credential also holds an authenticator (TOTP) setup key, it enters the six-digit code when the portal asks for one.
- Searches for the site by name and opens it. If more than one site has exactly that name, it stops rather than guess.
- Reads the site's device list: name, model, status, firmware and last seen. If you give a
device, keeps only devices whose name contains it. - Reads the site's open alerts.
- Prints a summary and returns the devices, offline devices and alerts as a structured result.
What it doesn't do:
- It can't open a device's local admin page, such as
http://192.168.1.1on a router or printer. See Devices with only a local admin page. - It doesn't change anything. It doesn't reboot devices, update firmware or clear alerts.
- It doesn't watch devices continuously. Each run is a one-off check; schedules repeat at most once a day.
- It doesn't complete push-notification or SMS sign-in challenges. Only an authenticator-app (TOTP) code works.
- It doesn't run on a device. Playwright actions run in the cloud sandbox only.
Devices with only a local admin page
Playwright actions run only in SuperIT's cloud sandbox, and the sandbox can't reach private network addresses. Private (RFC 1918), loopback and link-local addresses are blocked by a firewall and by a guard inside the script runner (see Network). A page such as http://192.168.1.1 on a router, switch or printer that is only reachable inside the office network can't be opened. If a script tries, the request is aborted and Stdout shows a line starting [net-guard] aborting. Playwright actions can't run on a device, so a browser action can't reach these pages.
What you can do instead:
- If the device is also managed through the vendor's cloud portal, use this guide against the portal.
- If the device has a local HTTP API or a status page that doesn't need a browser, a PowerShell action can query it from a Windows device on the same network. See Query an on-premises system from a device.
Before you begin
- The device is managed in the vendor's cloud portal, and you know the site (or network) name the portal uses.
- Check for an API first. Many cloud management portals have an API. If yours does and your plan includes it, use Call a third-party API instead; it's faster and doesn't break when the portal's layout changes.
- A portal account for automation, ideally a read-only or monitor-only role used only by SuperIT, so you can see its activity and revoke it without affecting a person.
-
Multi-factor authentication. If the portal requires it, choose one:
- Add the account's authenticator-app (TOTP) setup key to the credential as a
totp_seedrow. The script then generates the code itself. Storing the seed means SuperIT holds both factors for that account, so keep the account's rights to read-only. - Use a service account the vendor allows to sign in without MFA, for example from known IP addresses only. SuperIT's sandbox uses fixed outbound IP addresses (see Network).
Which options you have depends on the vendor. Check that your own security policy allows the one you choose before you use it. - A credential. Under Workflows → Credentials, select New, name it
Network Portaland choose Custom HTTP headers (under Advanced). Add rowsusernameandpassword, plustotp_seedif you use an authenticator code. The script reads them asCREDENTIAL_NETWORK_PORTAL_USERNAME,CREDENTIAL_NETWORK_PORTAL_PASSWORDandCREDENTIAL_NETWORK_PORTAL_TOTP_SEED. Values are encrypted at rest. See Credentials. - Check the portal's terms allow automated access. - Your role. Creating an action in chat needs the Admin role. Engineers can build it by hand under Workflows → Actions. See Approvals for who can run it. - Add the account's authenticator-app (TOTP) setup key to the credential as a
Build it with SuperIT
Describe the pages and what you see on them. SuperIT turns that into a Playwright script:
Create an action called "Check network devices in cloud portal".
Purpose: sign in to https://manage.network-vendor.example, open a site by name, and report each device's status, model, firmware and last seen, plus the site's open alerts. Read-only.
Runtime: Playwright/Python in the cloud sandbox.
Inputs: site (string, required), device (string, optional: only devices whose name contains this).
Credential: "Network Portal" (custom headers username, password and optional totp_seed).
Sign-in page: fields "Email" and "Password", button "Sign in". If totp_seed is set and the portal asks for a code, enter the 6-digit code in "Verification code" and click "Verify"; if it goes straight to the signed-in page, carry on.
Sites: search box "Search sites", then click the site's name. Devices tab: table with columns Name, Model, Status, Firmware, Last seen. Alerts tab: table with columns Severity, Device, Alert, Time. An empty table shows "No results"; fail if a table shows neither rows nor that message.
Never print the password or the code.
No authorization policy: engineers and admins only, no approval.
SuperIT drafts the script, validates it and creates the action as a draft. Portals differ, so expect to adjust the labels, tabs and column names after the first test. You review it, test it and publish it under Workflows → Actions.
Or build it yourself
| Name | Type | Required | Example |
|---|---|---|---|
site |
string | Yes | Leeds Office |
device |
string | No | AP-Floor2 |
Set Runtime to Playwright/Python. SuperIT starts the browser for you and provides page, browser_context and input_context. Use await at the top level, and assign output to return a result. main comes first and reads as the plan, each step is a small function below it, and the last line runs it.
Everything specific to the portal is at the top of the script: the address, the labels, the tab names and the column names. Change those to match your portal.
# Goal: sign in to the network vendor's cloud portal, open one site, and report its devices'
# status, firmware and last seen, plus open alerts. Read-only: it changes nothing.
import asyncio
import base64
import hashlib
import hmac
import os
import struct
import time
from playwright.async_api import TimeoutError as PlaywrightTimeoutError
# Placeholders: replace with your portal's address, labels, tab names and column names.
PORTAL = "https://manage.network-vendor.example"
LOGIN_PATH = "/login"
HOME_URL_PATTERN = "**/sites**"
LABELS = {"email": "Email", "password": "Password", "sign_in": "Sign in",
"code": "Verification code", "verify": "Verify",
"site_search": "Search sites", "devices_tab": "Devices", "alerts_tab": "Alerts"}
DEVICE_COLUMNS = {"name": "Name", "model": "Model", "status": "Status",
"firmware": "Firmware", "last_seen": "Last seen"}
ALERT_COLUMNS = {"severity": "Severity", "device": "Device", "alert": "Alert", "time": "Time"}
ONLINE_STATUSES = {"online", "connected", "up"}
EMPTY_TABLE_TEXT = "No results"
async def main(site, device_filter):
check_inputs(site)
username, password, totp_seed = read_credential()
await sign_in(username, password, totp_seed)
await open_site(site)
devices = filter_by_name(await read_devices(), "name", device_filter)
alerts = filter_by_name(await read_alerts(), "device", device_filter)
summary = {"site": site, "device_filter": device_filter or None, "devices": devices,
"offline": [d for d in devices if not is_online(d)], "alerts": alerts}
print_summary(summary)
return summary
def check_inputs(site):
if not site:
raise RuntimeError("Input 'site' is required.")
def read_credential():
username = os.environ.get("CREDENTIAL_NETWORK_PORTAL_USERNAME")
password = os.environ.get("CREDENTIAL_NETWORK_PORTAL_PASSWORD")
totp_seed = os.environ.get("CREDENTIAL_NETWORK_PORTAL_TOTP_SEED")
if not username or not password:
raise RuntimeError("The 'Network Portal' credential is missing username or password.")
return username, password, totp_seed
async def sign_in(username, password, totp_seed):
await page.goto(PORTAL + LOGIN_PATH, wait_until="domcontentloaded")
await page.get_by_label(LABELS["email"]).fill(username)
await page.get_by_label(LABELS["password"]).fill(password)
await page.get_by_role("button", name=LABELS["sign_in"]).click()
if totp_seed and await code_requested():
await enter_totp_code(totp_seed)
try:
await page.wait_for_url(HOME_URL_PATTERN, timeout=30000)
except PlaywrightTimeoutError:
raise RuntimeError("Sign-in didn't reach the sites page. Check the credential, "
"or whether the portal asked for MFA the script can't answer.") from None
print("Signed in.")
async def code_requested():
# Portals skip the code for a trusted sign-in, so wait for whichever comes first: the code box or the signed-in page.
waits = [asyncio.ensure_future(page.get_by_label(LABELS["code"]).wait_for(timeout=30000)),
asyncio.ensure_future(page.wait_for_url(HOME_URL_PATTERN, timeout=30000))]
done, pending = await asyncio.wait(waits, return_when=asyncio.FIRST_COMPLETED)
for task in pending:
task.cancel()
# A timeout here isn't fatal: sign_in's own wait for the signed-in page reports it.
failed = {task for task in done if task.exception() is not None}
return waits[0] in done and waits[0] not in failed
async def enter_totp_code(totp_seed):
code_box = page.get_by_label(LABELS["code"])
await code_box.fill(totp_code(totp_seed))
await page.get_by_role("button", name=LABELS["verify"]).click()
print("Authenticator code entered.")
def totp_code(seed):
seed = seed.replace(" ", "").upper()
key = base64.b32decode(seed + "=" * (-len(seed) % 8))
digest = hmac.new(key, struct.pack(">Q", int(time.time()) // 30), hashlib.sha1).digest()
offset = digest[-1] & 0x0F
return f"{(struct.unpack('>I', digest[offset:offset + 4])[0] & 0x7FFFFFFF) % 1000000:06d}"
async def open_site(site):
await page.get_by_label(LABELS["site_search"]).fill(site)
links = page.get_by_role("link", name=site, exact=True)
try:
await links.first.wait_for(timeout=15000)
except PlaywrightTimeoutError:
raise RuntimeError(f"No site called '{site}' was found in the portal.") from None
# Never pick one of several same-named sites: the report would describe the wrong site.
if await links.count() > 1:
raise RuntimeError(f"More than one site is called '{site}'. Rename one in the portal, or open it by URL.")
await links.first.click()
await page.get_by_role("tab", name=LABELS["devices_tab"]).wait_for(timeout=30000)
print(f"Opened site '{site}'.")
async def read_devices():
await page.get_by_role("tab", name=LABELS["devices_tab"]).click()
rows = await read_table(await wait_for_table(DEVICE_COLUMNS["firmware"]))
devices = [{key: row.get(column, "") for key, column in DEVICE_COLUMNS.items()} for row in rows]
print(f"Read {len(devices)} devices.")
return devices
async def read_alerts():
await page.get_by_role("tab", name=LABELS["alerts_tab"]).click()
rows = await read_table(await wait_for_table(ALERT_COLUMNS["alert"]))
alerts = [{key: row.get(column, "") for key, column in ALERT_COLUMNS.items()} for row in rows]
print(f"Read {len(alerts)} open alerts.")
return alerts
async def wait_for_table(column):
# Wait for the table with this column, not "networkidle": dashboards that keep polling never go
# idle, and the column tells the devices table from the alerts table on the same page.
# Headers can render before the rows, so only rows or the portal's empty message count as loaded.
table = page.locator("table", has=page.locator("thead th", has_text=column)).first
loaded = table.locator("tbody tr").or_(page.get_by_text(EMPTY_TABLE_TEXT, exact=True)).first
try:
await loaded.wait_for(timeout=30000)
except PlaywrightTimeoutError:
raise RuntimeError(f"The '{column}' table didn't load rows or show '{EMPTY_TABLE_TEXT}' within 30 seconds, "
"so the result would be wrong. Check the tab and column names.") from None
return table
async def read_table(table):
if await table.count() == 0:
return []
headers = [h.strip() for h in await table.locator("thead th").all_inner_texts()]
rows = []
for row in await table.locator("tbody tr").all():
cells = [c.strip() for c in await row.locator("td").all_inner_texts()]
# Skips a single-cell "no results" row, which would otherwise read as an offline device.
if len(cells) == len(headers):
rows.append(dict(zip(headers, cells)))
return rows
def filter_by_name(items, field, name_filter):
if not name_filter:
return items
needle = name_filter.lower()
return [item for item in items if needle in item[field].lower()]
def is_online(device):
return device["status"].strip().lower() in ONLINE_STATUSES
def print_summary(summary):
print(f"=== NETWORK DEVICES: {summary['site']} ===")
print(f"Devices : {len(summary['devices'])}, offline: {len(summary['offline'])}")
for d in summary["devices"]:
print(f"- {d['name']} | {d['model']} | {d['status']} | firmware {d['firmware']} | last seen {d['last_seen']}")
print(f"Open alerts : {len(summary['alerts'])}")
for a in summary["alerts"]:
print(f"- [{a['severity']}] {a['device']}: {a['alert']} ({a['time']})")
output = await main(
site=(input_context.get("site") or "").strip(),
device_filter=(input_context.get("device") or "").strip(),
)
Tips for portal scripts:
- Prefer
get_by_labelandget_by_rolewith the text you see on screen. They survive layout changes better than CSS selectors. - The script waits for each table to appear rather than for the page to go quiet, because dashboards that refresh themselves never stop loading. Each table must show rows, or the portal's empty-list message (
EMPTY_TABLE_TEXT, "No results" by default), within 30 seconds. Otherwise the run fails instead of reporting no devices or alerts, so setEMPTY_TABLE_TEXTto your portal's wording. - Many portals let you open a site directly by URL. If yours does, replace
open_sitewith apage.gototo that URL; it's quicker than searching. - If the portal's tables are built from
divelements rather thantable,read_tablefinds nothing. Read rows withpage.get_by_role("row")instead. - Status words differ between vendors. Add yours to
ONLINE_STATUSES, or every device shows as offline. - The script prints after each stage, so a failed run shows how far it got. It never prints the password or the authenticator code.
Test it
- Save the draft and select Test.
- Test is not a dry run: it skips authorization and approvals and uses the real credential and network access, so it really signs in to the portal. The action only reads. Test runs aren't billed.
- Run it for a site you know, and compare the device list, statuses and alerts with the portal.
- If it fails, read Stdout for the last stage reached and Stderr for the error, adjust the labels, tabs or column names, and select Run again.
- Select Publish.
Use it
- In chat: "Is the Wi-Fi at the Leeds office up?" SuperIT runs the action with
siteset toLeeds Office, then tells you which access points are offline and what alerts are open. - During a ticket: when a user reports the Wi-Fi or a printer is down, run it and link the run to the ticket, so completion and error notes post to the PSA ticket. Combine it with what SuperIT already sees on the user's Windows device.
- Firmware review: "Which firmware are the switches at each site on?" Run it per site and compare.
- Managed teams: if each customer has its own portal login, use Publish to teams. Each managed team gets a read-only copy and a blank
Network Portalcredential for its admin to fill in. If you use one partner login for every customer, keep the action in the service provider team and pass the customer's site name.
Variations
- Daily check with an alert: schedule it every day and post only when a device is offline or a critical alert is open, as in Post a message to a Teams or Slack channel. A schedule doesn't send the output anywhere by itself.
- Firmware drift: compare each model's firmware with the newest version seen across the site, and list the devices behind.
- Managed print: read toner levels and page counts from a managed print portal instead of firmware.