Why is my crypto.getRandomValues() base36 ID generator producing duplicates despite 2.8 trillion possibilities?

Here is the function:

export function generateId(): string {
  const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  let result = "";

  const randomArray = new Uint8Array(8);
  crypto.getRandomValues(randomArray);

  for (let i = 0; i < 8; i++) {
    result += chars[randomArray[i] % 36];
  }

  return result;
}

It should generate 8-character random base36 IDs, e.g. QZ08HNDL. Base36 uses 0-9 and A-Z (36 characters). If the id is 8 characters = 36^8 = ~2.8 trillion possibilities.

But I’ve had some “INVALID_RECORD: record is invalid and can’t be saved. ID is not unique” errors reported in the last few days, and I’m not sure if it’s because this function is generating duplicates. How likely is it? Are there flaws in this function?

How to design extensible data aggregation code to avoid manual updates in multiple places?

I have a data aggregation function that collects statistics from multiple sources. The problem is that whenever I add a new metric, I need to manually update the code in three different places, which is error-prone and hard to maintain.

Current Code Structure:

// Part 1: Initialize data object
const storeData = {
    collectors_count: 0,
    toys_count: 0,
    toys_inspected: 0,
    toys_cleaned: 0,
    inspection_percent: 0,
    cleaning_percent: 0,
};

// Part 2: Accumulate data from each source
for (const collector of collectors) {
    const details = await db.getCollectorDetails(collector.id);
    
    if (!details) continue;

    storeData.collectors_count++;
    storeData.toys_count += details.toy_count;
    storeData.toys_inspected += details.inspected_count;
    storeData.toys_cleaned += details.cleaned_count;
}

// Part 3: Calculate derived metrics
storeData.inspection_percent = (storeData.toys_inspected / storeData.toys_count) * 100;
storeData.cleaning_percent = (storeData.toys_cleaned / storeData.toys_count) * 100;

return storeData;

The Problem:

  • When I want to add a new metric (e.g., toys_repaired), I have to:
  • Add it to the initial object with value 0
  • Add accumulation logic in the loop: storeData.toys_repaired += details.repaired_count
  • Possibly add calculation logic if it’s a derived metric: storeData.repair_percent = ...

This becomes tedious with many metrics and I sometimes forget to update all three places.

Is there a way to refactor this code to be more extensible and maintainable?

How to make it so that the arrays inside the array don’t get changed?

I am trying to make a loop that will fill in an array of arrays that will contain every possible colour value in rgb form given the bit depth. However, I have noticed that even after an array is pushed into the larger array, whenever the code adds to a new array, it also increments by the same value. I believe this is because technically all the arrays have the same name, but I don’t have an alternative to that.

this is the code I’m using:

var colours = []
var c = [0, 0, 0]
for (let x = 0; x < 4; x++) {
  c[1] = 0
  for (let y = 0; y < 4; y++) {
    c[2] = 0
    for (let z = 0; z < 4; z++) {
      const v = c
      colours.push(v)
      c[2] += 85
    }
    c[1] += 85
  }
  c[0] += 85
}
console.log(colours)

previously there were another bunch of errors, but I resolved them by resetting certain things after every loop. I also tried to duplicate the array using const v but it doesn’t work. currently, when I run this the console shows this:

// [object Array] (64)
[// [object Array] (3)
[340,340,340],// [object Array] (3)
[340,340,340],// [object Array] (3)
[340,340,340],// [object Array] (3)

for 64 rows, but what I’m looking for is something like this:

[[0,0,0],[0,0,85],[0,0,170],[0,0,255],[85,0,0]]

containing every combination until [255,255,255]

Wat to Wasm 3.0

I want to work in the latest “Wasm” 3.0
But the latest version of “wabt” doesn’t convert “wat” which is for the “wasm” 3.0.
Is there is ant other alternative way? and i also want that way to be in the browser, as i am also making a thing which needs “wat” content to converted to “wasm” 3.0 in the browser itself

I tried “wabt”, and “wasm-tools”

“Wasm-tools” have “wat” to “wasm” 3.0 but it doesn’t have the browser conversion.

Expo vs React Native CLI in 2025 – Which one should I choose for a production fintech app? [closed]

I’m starting a new production-grade fintech app in 2025 (similar to a payments/marketplace product).

I’m confused whether I should use Expo or stick with React Native CLI.

My requirements include:

  1. OTA updates for faster bug fixes and translations

  2. Biometric authentication (Face ID / Fingerprint)

  3. Payment gateway integration (using WebView, but possibly native SDKs in the future)

  4. Webhooks, redirects, and deep linking support

  5. Long-term maintainability and enterprise-grade stability (financial product use case)

From what I know, Expo has improved a lot in 2025 with config plugins and prebuilds, but I want to be sure:

  • Are there still any limitations of Expo compared to bare RN?

  • Is Expo stable enough for fintech/enterprise apps in 2025?

  • Which approach is better for a long-term project with heavy native dependencies?

Country code gets populated in the local number part intermittently when using MuiTelInput

When the user selects the country code from the dropdown. It gets populated in the local number part. I have to reconstruct the phone number for

import Keyboard from 'react-simple-keyboard';
import 'react-simple-keyboard/build/css/index.css';
import { MuiTelInput } from 'mui-tel-input';
    const getCountryCode = (phoneStr) => {
    const match = phoneStr.match(/^+d+/);
    return match ? match[0] : '';
};

const getLocalNumber = (phoneStr) => {
    const countryCode = getCountryCode(phoneStr);
    return phoneStr.replace(countryCode, '').replace(/D/g, '');
};
<MuiTelInput
                key={telInputKey}
                fullWidth
                value={formData.phone}
                onChange={(value) => {
                const countryCode = getCountryCode(value) || '+39';
                let localNumber = getLocalNumber(value);
                localNumber = localNumber.replace(/D/g, '').slice(0, 10); // Force 10 digits max
                const newPhone = `${countryCode} ${localNumber}`;
                setFormData({ ...formData, phone: newPhone });
                }}
                onFocus={() => setFocusedField('phone')}
                defaultCountry={country}
                forceCallingCode
                label={t('labelPhone')}
                sx={{ width: '25rem'}}
            />

I have a onscreen keyboard to get the input from the user

if (button === '{bksp}') {
      if (focusedField.startsWith('phone')) {
        setFormData(prev => {
          const countryCode = getCountryCode(prev.phone) || "+39";
          let localNumber = getLocalNumber(prev.phone);
          localNumber = localNumber.slice(0, -1);
          const newPhone = localNumber ? `${countryCode} ${localNumber}` : countryCode;
          return { ...prev, [focusedField]: newPhone };
        });
      }

Modal window in wagtail page editing interface

I want to add a custom BlockFeature and for that i need a modal window for input. I don’t understand how to make a modal input window so that the entred data will go straight to my custom blockfeature props.

The feature i’m working on is an ordered list that starts with a given number (). So yeah, basically, i need that modal window to enter the starting number

@hooks.register('register_rich_text_features')
def register_ol_custom_feature(features):
    feature_name = "ol-custom"
    type_ = "ol-custom"
    control = {
        "type": type_,
        "label": "ol+",
        "element": "ol",
        "description": "Set start",
    }

    features.register_editor_plugin(
        'draftail',
        feature_name,
        BlockFeature(
            control,
            js=["static/js/orderedListCustom.js"],
        ),
    )

    db_conversion = {
        "from_database_format": {
            "ol[start]": BlockElementHandler(type_)
        },
        "to_database_format": {
            'block_map': {
                type_: lambda props: DOM.create_element(
                    "ol",
                    {"start": props.get("start") if "start" in props else 1}
                )
            }
        },
    }

    features.register_converter_rule("contentstate", feature_name,  db_conversion)
    features.default_features.append(feature_name)

I tried several js codes that i found online or got from ai, none of them worked (my bad, i suck at js). Also came across one post on here, but it didn’t help (:
In the end, i’m not even sure what things i need to consider/add/alter, what’s the workflow here.
Can anyone help?

How to disable radio button on all levels in a recursive Angular form component based on a specific level’s value

this is kind of a continuation of an improved version of an old question of mine
so basically I have a recursive Angular form and I’m using to manage a folder hierarchy. Each folder has a radio button for selecting between Exclusive and Regular folder.

Now I’m trying to add a validation so that

  • When a folder is selected as Exclusive at any level in the hierarchy the Exclusive button should be disabled for all other folders at any level. (basically 1 exclusive folder in the folder hierarchy)
  • The Regular option should remain available at all levels regardless of whether Exclusive is selected at any other level

How should I even approach a problem like this because the notification should happen from parent to child level and also from child level to parent level. Is this possible to be done with @Input() or is there a better approach.

I present demo version here for clarity. still a bit long to read but here is reproducible demo in stackblitz with same code

form component

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, Validators, AbstractControl } from '@angular/forms';
import { duplicateFolderName } from '../validators/duplicate-folder-name.validator';

@Component({
  selector: 'my-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css'],
})
export class FormComponent implements OnInit {
  myForm!: FormGroup;
  isHierarchyVisible = false;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      folderHierarchy: this.fb.array([]),
    });
    if (!this.folderHierarchy.length) this.isHierarchyVisible = false;
  }

  addFolder() {
    this.folderHierarchy.push(
      this.fb.group({
        name: [null, [Validators.required, duplicateFolderName()], { updateOn: 'blur' }],
        isExclusive: false,
        subFolders: this.fb.array([]),
        level: 0,
      })
    );
    this.isHierarchyVisible = true;
  }

  removeFolder(index: number) {
    this.folderHierarchy.removeAt(index);
    if (!this.folderHierarchy.length) this.isHierarchyVisible = false;
  }

  get folderHierarchy() { return this.myForm.get('folderHierarchy') as FormArray; }
  getForm(control: AbstractControl) { return control as FormGroup; }
}

<p>folder form. type in form name and press enter</p>
<form [formGroup]="myForm">
  <div formArrayName="folderHierarchy">
    <label for="folderHierarchy">create folder </label>
    <div>
      <button type="button" class="btn btn-custom rounded-corners btn-circle mb-2" 
              (click)="addFolder()" [disabled]="!folderHierarchy.valid">Add</button>
      <span class="pl-1">new folder</span>
    </div>
    <div *ngIf="!folderHierarchy.valid" class="folder-hierarchy-error">invalid folder hierarchy</div>
    <div class="folderContainer">
      <div *ngFor="let folder of folderHierarchy.controls; let i = index" [formGroupName]="i">
        <folder-hierarchy (remove)="removeFolder(i)" [folder]="getForm(folder)" [index]="i"></folder-hierarchy>
      </div>
    </div>
  </div>
</form>

folder-hierarchy component

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { duplicateFolderName } from '../validators/duplicate-folder-name.validator';

@Component({
  selector: 'folder-hierarchy',
  templateUrl: './folder-hierarchy.component.html',
  styleUrls: ['./folder-hierarchy.component.css'],
})
export class FolderHierarchyComponent {
  @Output() remove = new EventEmitter();
  @Input() folder!: FormGroup;
  @Input() index!: number;
  @Input() parentDirectory?: FormGroup;

  constructor(private fb: FormBuilder) {}

  addSubFolder(folder: FormGroup): void {
    (folder.get('subFolders') as FormArray).push(
      this.fb.group({
        name: [null, [Validators.required, duplicateFolderName(this.parentDirectory)]],
        isExclusive: false,
        subFolders: this.fb.array([]),
        level: folder.value.level + 1,
      })
    );
  }

  updateIsExclusive(folder: FormGroup, value: Event, isExclusive: boolean): void {
    folder.get('isExclusive')?.setValue(isExclusive ? (value.target as HTMLInputElement).checked : !(value.target as HTMLInputElement).checked);
  }

  getControls(folder: FormGroup): FormArray {
    return folder.get('subFolders') as FormArray;
  }

  removeSubFolder(folder: FormGroup, index: number): void {
    (folder.get('subFolders') as FormArray).removeAt(index);
  }

  removeFolder(folder: FormGroup): void { this.remove.emit(folder); }

  disableAdd(folder: FormGroup): boolean { return this.folder.invalid || folder.invalid; }

  get nameControl(): FormControl { return this.folder.get('name') as FormControl; }
}

<div *ngIf="folder" #folderRow class="folder-row" [formGroup]="folder">
  <div class="folder-header">
    <div class="folder-name-container">
      <label for="folderName" class="folder-name-label">Name:</label>
      <input #folderName id="folderName" [ngClass]="nameControl.errors ? 'invalid-input' : ''"
        class="folder-name-input" placeholder="Folder Name" type="text" maxlength="50" autocomplete="off"
        name="name" formControlName="name" (keyup.enter)="folderName.blur()" />
    </div>
    <div class="folder-type-container">
      <div class="folder-type-option">
        <input type="radio" [value]="true" [checked]="folder.value.isExclusive" (change)="updateIsExclusive(folder, $event, true)" />
        <label for="exclusive">Exclusive</label>
      </div>
      <div class="folder-type-option">
        <input type="radio" [value]="false" [checked]="!folder.value.isExclusive" (change)="updateIsExclusive(folder, $event, false)" />
        <label for="regular">Regular</label>
      </div>
    </div>
    <button type="button" class="btn-remove-folder" (click)="removeFolder(folder)">Remove</button>
    <button type="button" class="btn-add-subfolder" [disabled]="disableAdd(folder)" (click)="addSubFolder(folder)">Add Subfolder</button>
  </div>
  <div *ngIf="folder && folder.value.subFolders.length > 0" class="subfolder-container">
    <div *ngFor="let subFolder of getSubFoldersControls(folder); let i = index" class="subfolder-item">
      <folder-hierarchy (remove)="removeSubFolder(folder, i)" [folder]="subFolder" [parentDirectory]="getSubFolderParent(subFolder)"></folder-hierarchy>
    </div>
  </div>
  <div *ngIf="nameControl.errors?.required" class="folder-hierarchy-error">Name is required.</div>
  <div *ngIf="nameControl.errors?.duplicateName" class="folder-hierarchy-error">Name already exists</div>
</div>

Help is much appreciated

Using the fill prop in Next Image but the images are not showing

I’m mapping the images to display them. I’m using fill prop of Next Image but the images are not showing in the browser. It’s showing the moment I use width and height props.
I could have gone with the height and width props but that doesn’t help in maintaining consistent dimensions for the images.

I’m not getting what’s the issue

Here’s the code:

<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
  {japandiImages.map((image, index) => (
    <div key={index} className="relative w-full h-64">
      <Image
        src={image.src}
        alt={image.alt}
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 33vw, 33vw"
        className="object-cover rounded-lg shadow"
      />
    </div>
  ))}
</div>

Here’s the link to the GitHub repo in case you wanna see the whole code. The problem is in the route folder named Japandi:
Link to the repo

  1. I tried using width and height props of Next Image and it works. However, I can’t use them because they make the dimensions inconsistent for the images.

  2. On some YT video, they say to use sizes props along with fill. I tried that too, but it’s still not working.

My cshtml table cant be populated dynamically by the javascript api call

I recently started learning web development with netcore 8 and i simply want to make a table with CRUD. Im using existing resources on SSMS for the database. But I cant even get the table to be populated.

The table inside my Index.cshtml:

     <!-- Table -->
     <div class="table-responsive">
         <table id="userTable" class="table table-striped table-bordered align-middle">
             <thead class="table-light">
                 <tr>
                     <th>Employee ID</th>
                     <th>Name</th>
                     <th>Role</th>
                     <th>Department</th>
                     <th>Date Created</th>
                     <th style="width: 100px;">Aksi</th>
                 </tr>
             </thead>
             <tbody></tbody> <!-- populated by UserManagement.js-->
         </table>
     </div>

     <!-- Pagination -->
     <nav aria-label="User table pagination">
         <ul class="pagination justify-content-end">
             <li class="page-item disabled"><a class="page-link">«</a></li>
             <li class="page-item active"><a class="page-link" href="#">1</a></li>
             <li class="page-item"><a class="page-link" href="#">2</a></li>
             <li class="page-item"><a class="page-link" href="#">3</a></li>
             <li class="page-item"><a class="page-link" href="#">»</a></li>
         </ul>
     </nav>
 </div>
<!-- ApiBaseUrl is "https://localhost:7043/api/", its originated from "ApiSettings" inside appsettings.json -->
 <script> const API_BASE_URL = '@ViewBag.ApiBaseUrl';</script>

<!-- chatgpt told me to use this, previously i used src="~js/UserManagement.js", but it doesnt work, I put the script inside wwwrooot/js/UserManagement.js-->
<script src="@Url.Content("~/js/UserManagement.js")" asp-append-version="true"></script>

My UserManagement.js:

console.log("JS loaded, API_BASE_URL =", API_BASE_URL);
//I desperately want to see any output that suggest this script is loaded, so you will see a bunch of these, but Console tab in DevTools is totally empty.

document.addEventListener("DOMContentLoaded", function () {
    loadUsers();
    document.querySelector("#btnSearch").addEventListener("click", function () {
        const keyword = document.querySelector("#txtSearch").value;
        loadUsers(1, keyword);
    });
});

// this is the function that should populate the table

function loadUsers(page = 1, search = "") {
    const url = `${API_BASE_URL}Users?page=${page}&pageSize=10&search=${encodeURIComponent(search)}`;
    console.log("Fetching:", url);
    fetch(url)
        .then(response => response.json())
        .then(result => {
            const tbody = document.querySelector("#userTable tbody");
            tbody.innerHTML = "";

            result.users.forEach(user => {
                const row = `
                    <tr>
                        <td>${user.employeeId ?? "-"}</td>
                        <td>${user.name ?? "-"}</td>
                        <td>${user.roleName ?? "-"}</td>
                        <td>${user.dept}</td>
                        <td>${formatDate(user.createDate)}</td>
                        <td>
                            <button class="btn btn-sm btn-light"><i class="si si-pencil"></i></button>
                            <button class="btn btn-sm btn-light text-danger"><i class="si si-trash"></i></button>
                        </td>
                    </tr>`;
                tbody.insertAdjacentHTML("beforeend", row);
            });
        })
        .catch(error => console.error("Error fetching users:", error));
}

function formatDate(dateStr) {
    const date = new Date(dateStr);
    return date.toLocaleString("id-ID", {
        day: "2-digit",
        month: "long",
        year: "numeric",
        hour: "2-digit",
        minute: "2-digit"
    });
}

document.addEventListener("DOMContentLoaded", () => {
    consol.log("DOM fully loaded and parsed");
    loadUsers();
});
console.log("Script loaded");

my UserManagementController.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using MyTableApp.Config;

namespace MyTableApp.Controllers
{
    public class UserManagementController : Controller
    {
        readonly ApiSettings _apiSettings;

        public UserManagementController(IOptions<ApiSettings> apiSettings)
        {
            _apiSettings = apiSettings.Value;
        }

        public IActionResult Index()
        {
            ViewBag.ApiBaseUrl = _apiSettings.BaseUrl;
            return View();
        }
    }
}

and lastly, my UsersController.cs

using Microsoft.AspNetCore.Mvc;
using MyTableApi.Models.Entities.DB_FOR_LEARNING_NET8;

namespace MyTableApi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class UsersController : ControllerBase
    {
        readonly DbForLearningNet8Context _context;

        public UsersController(DbForLearningNet8 context)
        {
            _context = context;
        }

        [HttpGet]
        public IActionResult GetUsers(
            int page = 1,
            int pageSize = 10,
            string? search = null)
        {
            var query = _context.VwEmployeeDetails.AsQueryable();

            if(!string.IsNullOrWhiteSpace(search))
            {
                query = query.Where(u => u.EmployeeID.Contains(search) || u.Name.Contains(search));
            }

            var totalUsers = query.Count();

            var users = query
                .OrderByDescending(u => u.CreateDate)
                .Skip((page - 1) * pageSize)
                .Take(pageSize)
                .ToList();

            return Ok(new
            {
                totalUsers,
                page,
                pageSize,
                users
            });
        }
    }
}

So when I try to run the api and web app project, the table is empty. Nothing inside of Console on DevTools. The only references to UserManagement.js I can find inside Network tab is this UserManagement.js?v=”some_random_string” with the status 200 and Users?page=1&pageSize=10&search= with the status 404 (fetch).

Im sorry if I send too many codes, I have been trying to fix this for hours and yet nothing seems to fix it, it makes no sense to me as how is it showing that it was able to reference UserManagement.js but not a single console.log is printed in the Console tab?
I appreciate any kind of help or knowledge about Net Core 8!

Why does URL Pattern API accept wildcard at the beginning of pathname?

The pathname URL-part always starts with /. If you omit the / in your regular expression the match will fail

URL Pattern API (pathname matching)

Let leading slash be true if the first code point in value is U+002F (/) and otherwise false

WHATWG (canonicalize a pathname)

The pathname options is an options struct with delimiter code point set “/” and prefix code point set to “/”

WHATWG (pathname options)

Why is the output true if the string doesn’t start with “/”?

console.log(
  new URLPattern({pathname: '*/books'}).test({pathname: '/api/v1/books'})
)

Is it a hidden feature or did I miss some point in MDN or in WHATWG?

How can I include the file name (or origin) where a function is executed?

I have event bus for mFEs and i want to add filename to each dispatched object to know from where dispatched event comes.

i have solution which looks ugly, in dispatch function every time create Error and find it’s origin:

const err = new Error();
if (!err.stack) {
    return {};
}

const callerLine = err.stack.split("n")[3];

is there better way to do it without writing filename in each file?

How to safely capture both toast title and message in Playwright without waiting timeout?

I’m working with Playwright (Node.js) and need to capture notification toasts that appear on the page.
The issue is that the toasts don’t always have the same structure:

  • Some have only a message

  • Some have a title and a message

  • They also change their class (toast-info, toast-warning, toast-error, etc.)

When I try to capture both the title and the message, Playwright sometimes freezes, waiting for .toast-title when it doesn’t exist. I need a safe and generic way to capture all possible cases without running into timeouts.

<!-- example of toast with only message -->
<div id="toast-container" class="toast-top-right" aria-live="polite" role="alert">
  <div class="toast toast-info" style="display: block;">
    <button class="toast-close-button" role="button"><i class="ion-android-close"></i></button>
    <div class="toast-message">Nota Lesión Activa: Lesiones 364</div>
  </div>
</div>

<!-- example of toast with title and message -->
<div id="toast-container" class="toast-top-right" aria-live="polite" role="alert">
  <div class="toast toast-error" style="display: block;">
    <button class="toast-close-button" role="button"><i class="ion-android-close"></i></button>
    <div class="toast-title">Error</div>
    <div class="toast-message">Paciente no encontrado</div>
  </div>
</div>

Error observed

Sometimes my test fails with a timeout when trying to get the toast title:

Get text content(
page.locator('#toast-container .toast').first().locator('.toast-title')
)
— 30.0s
waiting for locator('#toast-container .toast').first().locator('.toast-title')
Timeout 30000ms exceeded.

What I tried

I created two functions: one (boton) that clicks a button and checks for toasts, and another (Capturaerror) that extracts the toast-title and toast-message.

async function boton(page, Accion, indice = 0) {
    await page.getByRole('button', { name: Accion }).nth(indice).click({ timeout: 1000 });
    const toasts = page.locator('#toast-container .toast, #toast-container .toast-error, #toast-container .toast-warning');
    const count = await toasts.count();
    const mensajes = [];

    for (let i = 0; i < count; i++) {
        const toast = toasts.nth(i);
        const clase = await toast.getAttribute('class') || '';
        const mensaje = await toast.locator('.toast-message').textContent().catch(() => '');

        if (clase.includes('toast-error')) {
            if (mensaje && mensaje.trim() !== '') {
                mensajes.push(`${i + 1}.- ${mensaje.trim()}`);
            }
        } else if (clase.includes('toast-warning')) {
            const msj = await Capturaerror(page);
            if (msj && msj.length > 0) {
                throw new Error(`${msj.join('n')}`);
            }
        } 
    }

    if (mensajes.length > 0) {
        throw new Error(mensajes.join("n"));
    }
}

async function Capturaerror(page) {
    const toasts = await page.locator('#toast-container .toast').all();
    const mensajes = [];

    let index = 1;
    for (const toast of toasts) {
        const titulo = await toast.locator('.toast-title').textContent().catch(() => '');
        const mensaje = await toast.locator('.toast-message').textContent().catch(() => '');
        if (titulo || mensaje) {
            mensajes.push(`${index}.- ${titulo.trim()}: ${mensaje.trim()}`);
            index++;
        }            
    }
    return mensajes;
}

What I expected

To always capture the visible text of each toast, regardless of whether it has a title, a message, or both.
The .catch(() => '') should skip missing .toast-title without freezing.


What actually happens

If a toast does not contain a .toast-title, Playwright waits for it until the timeout (30s), which causes the test to freeze.


Notes

  • I want a single generic solution that safely captures the title and/or message, or the whole toast text, without timeouts.

  • Node.js / Playwright environment.

usememo hook and usecallback hook

where to use usememo hook and usecallback hooks

Explanation for the Usememo and usecallback hooks and their usage and main differences between these hooks because this hooks are using for the similar usage (memoization).