Címke: Gemini

  • Playwright 1.64 újdonságok

    A Playwright 1.64-es verziója számos olyan új funkciót és finomítást hozott, amelyek megkönnyítik az elemkeresést, a tesztek futtatását, valamint a videófelvételek testreszabását.

    1. Új Locator API: locator.within()

    A locator.within() segítségével két már létező locatort lehet természetes módon kombinálni, és a relatív pozicionálók (pl. .first(), .nth()) a szülő elemen belül értékelődnek ki.

    TypeScript

    // Összekapcsol két külön definíciót
    const dialog = page.getByRole('dialog');
    const saveButton = page.getByRole('button', { name: 'Mentés' });
    
    // Mentés gomb keresése kifejezetten a párbeszédablakon belül
    await saveButton.within(dialog).click();
    

    2. Véletlenszerű tesztfuttatás: --shuffle

    Segít kiszűrni a tesztek közötti rejtett függőségeket a futtatási sorrend megkeverésével. A tesztfutás végén megadott seed kód segítségével pontosan reprodukálható a hibás futási sorrend.

    Bash

    # Tesztek véletlenszerű sorrendben történő futtatása
    npx playwright test --shuffle
    
    # Pontos futási sorrend újrajátszása a kapott seed alapján
    npx playwright test --shuffle 271828182
    

    3. Tesztzárolások (Test Locks) csoportszinten

    Míg a 1.63-ban megjelent zárolások csak egyes tesztekre vonatkoztak, a 1.64-ben már egész fájlokra vagy tesztcsoportokra (describe) is alkalmazhatóak. Az azonos zárolási névvel rendelkező tesztek nem futnak párhuzamosan.

    TypeScript

    import { test } from '@playwright/test';
    
    // Az ebben a csoportban lévő tesztek nem futnak párhuzamosan más 'user-settings' zárral ellátott tesztekkel
    test.describe.configure({ lock: 'user-settings' });
    
    test('profil frissítése', async ({ page }) => {
      // ...
    });
    

    4. default: false beállítás a projekt konfigurációban

    A playwright.config.ts-ben megjelölhetők olyan böngészők vagy projektek, amelyeket alapértelmezetten ki szeretnél hagyni a sima npx playwright test futtatásból anélkül, meglévő konfigurációkat kellene törölni.

    TypeScript

    // playwright.config.ts
    export default {
      projects: [
        { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
        { name: 'firefox', use: { ...devices['Desktop Firefox'] }, default: false },
        { name: 'webkit', use: { ...devices['Desktop Safari'] }, default: false },
      ],
    };
    
    • Futtatás csak chromium-on: npx playwright test
    • Kifejezett futtatás firefox-on: npx playwright test --project=firefox

    5. Videófelvételek fejlesztése (VP9, CSS stílusok, FPS)

    A videók rögzítése hatékonyabbá és jobban testreszabhatóvá vált:

    • VP9 kódolás: Kisebb fájlméret és alacsonyabb CPU-használat.
    • Egyedi képkockasebesség (FPS): Testreszabható fps opció.
    • CSS stílusok a műveletjelzőkhöz: A korábbi fontSize opció helyett CSS szabályok adhatók meg a mutatók és kiemelések testreszabására.

    TypeScript

    // playwright.config.ts
    export default {
      use: {
        video: {
          mode: 'on',
          fps: 60,
          style: {
            pointMarker: 'background-color: red;',
            targetHighlight: 'border: 2px solid blue;',
          },
        },
      },
    };
    

    6. Kísérleti WebMCP támogatás (page.webmcp)

    A Playwright kiegészült a böngésző alapú AI modellek eszközeinek (WebMCP) tesztelésére szolgáló felülettel (page.webmcp.tools() és page.webmcp.callTool()), amellyel ellenőrizhetők az oldalak által az AI-ágensek felé regisztrált eszközök.

    Fontosabb breaking change-ek / Viselkedésbeli változások

    • Rejtett iframe-ek: A nem látható iframe-ben lévő elemeket (pl. visibility: hidden) a Playwright mostantól automatikusan rejtettként kezeli az akciók és a toBeVisible() ellenőrzések során.
    • Emulált képernyőméret: A devices['Desktop Chrome'] típusú eszközleírók átadják a screen tulajdonságot, így a window.screen felbontása és a media query-k az emulált kijelzőméretet tükrözik.
    • Snapshot frissítés: A --update-snapshots=missing opció használata esetén a hiányzó screenshotok létrehozása után a teszt sikeresként értékelődik.
  • Felfedező tesztelés AI-val

    Egyre lustább vagyok, és az idő is nagy ellenség lett a munkában, így sok esetben jól tud jönni az extra segítség, amit manapság AI formájában kaphatunk meg. Én személy szerint nagyon sok szakmai témában konzultálok vele, mielőbb neki kezdenek, de vannak olyan témák is, amiről kevés információm van, csak hallottam valahol, így időm engedtével rákeresek. Ilyen az AI-val történő exploratory tesztelés is.

    Az exploratory testing (magyarul felfedező tesztelés) a szoftvertesztelés egy olyan megközelítése, ahol a tesztek megtervezése, végrehajtása és a szoftver megismerése egyszerre, párhuzamosan történik.

    A hagyományos (szkriptelt) teszteléssel ellentétben – ahol a tesztelő előre megírt, lépésről lépésre követendő teszteseteket (test cases) hajt végre – a felfedező tesztelésnél a tesztelő a saját intuitív tudására, domain-ismeretére, tapasztalatára és kíváncsiságára támaszkodik.

    A felfedező tesztelés fő jellemzői:

    1. Szabadság és rugalmasság: Nincsenek szigorúan előre leírt tesztlépések. A tesztelő menet közben dönt arról, hogy az alkalmazás melyik részét milyen irányban vizsgálja tovább, reagálva a szoftver tényleges viselkedésére.
    2. Tanulás és alkalmazkodás: Ahogy a tesztelő használja a szoftvert, egyre jobban megérti annak működését és gyenge pontjait. Ha a szoftver egy ponton gyanúsan viselkedik, a tesztelő azonnal mélyebbre áshat abban a témában.
    3. Kreativitás és hibatalálgatás (Error Guessing): Olyan egyedi, nem szokványos felhasználói útvonalakat és szélsőséges eseteket (edge case-eket) igyekszik feltárni, amelyekre a fejlesztők vagy a specifikációt írók nem gondoltak.
    4. Alacsony előkészítési igény: Mivel nem kell előre bonyolult tesztdokumentációt írni, nagyon gyorsan elindítható, így ideális agilis (Agile/Scrum) környezetben.

    Mit nem jelent az exploratory tesztelés?

    • Nem ad-hoc kattintgatás („monkey testing”): A felfedező tesztelés nem céltalan próbálkozás. Bár nincs előre megírt forgatókönyv, a tesztelőnek világos célja és stratégiája van.
    • Célzott keretek (Session-Based Testing): A gyakorlatban a felfedező tesztelést gyakran strukturált keretek között végzik:
      • Charter (Küldetés): Meghatározzák a tesztelés célját (pl. „Fedezd fel a fizetési folyamatot a lejárt bankkártyák kezelésének szempontjából”).
      • Időkeret (Time-box): A tesztelést egy rögzített idősávra korlátozzák (pl. 60–90 perc).
      • Jegyzőkönyv / Összefoglaló: A tesztelő rögzíti, mit tapasztalt, milyen problémákat talált és merre érdemes tovább kutatni.

    Összességében a felfedező tesztelés nem a szkriptelt vagy automatizált tesztek helyettesítője, hanem azok kiegészítője: az automatizált tesztek ellenőrzik, hogy a már meglévő funkciók működnek-e, míg a felfedező tesztelés segít megtalálni az új, váratlan hibákat.

    Az exploratory (felfedező) tesztelés természeténél fogva egy ember-központú, kreatív folyamat, ahol a tesztelő intuíciója, domain-ismerete és kíváncsisága vezet. Az AI itt nem a tesztelőt váltja fel, hanem hihetetlenül hatékony asszisztensként (co-pilot) működik, amely leveszi a válláról a monoton feladatokat, és új, nem várt tesztforgatókönyveket javasol.

    Mivel hallottam róla, hogy vannak, akik AI segítéséggel csinálják ezt, megkérdeztem az AI-t, hogy mit tud róla, és kaptam is egy listát. Ahogy lesz időm, kedvem, bele fogom ásni magam a témába.

    Addig is itt a válasz a Gemini-től:

    Az AI-alapú exploratory tesztelés megközelítései és eszközei méret és cél szerint három fő kategóriába oszthatók:

    1. AI-alapú Böngésző- és Autonóm Tesztelő Ügynökök (Autonomous AI Testing Agents)

    Ezek az eszközök képesek önállóan „bejárni” a webalkalmazásokat, feltérképezni a felületet, hibákat keresni, és közben segítik a felfedezést.

    • Open-source / DIY megközelítés (Playwright + LLM-alapú keretrendszerek):
      • Stagehandvagy Browser-use / Explorbot: Ezek modern, nyílt forráskódú keretrendszerek, amelyek ráköthetők a nagyobb nyelvi modellekre (pl. Claude vagy GPT). Természetes nyelven vagy célok megadásával (pl. „Keresd meg az űrlapon a hiányzó validációkat”) képesek irányítani a böngészőt, elemezni a DOM-ot, és olyan edge case-eket (szélsőséges eseteket) is letesztelni, amiket egy hagyományos script nem érne el.
    • Kereskedelmi platformok (Mabl, Functionize, Testim):
      • Ezekben az AI-alapú platformokban van ún. Agentic vagy intelligens felfedező funkció is.Amikor új funkciót raksz ki, az AI képes önállóan „körbejárni” az oldalt, vizuális anomáliákat és hibaüzeneteket keresni a konzolon, így támogatva az exploratory tesztelési fázist.

    2. AI Asszisztensek és Teszttervező Co-pilotok (Ötleteléshez és Charterekhez)

    Ha hagyományos, session-based exploratory tesztelést (SBET) csinálsz, az AI kiválóan használható partnerré válik a tesztelési „charterek” és ötletek generálásában.

    • ChatGPT / Claude / Cursor (Prompt alapú teszttervezés):
      • Hogyan használd? Másold be egy új funkció specifikációját vagy user story-ját, és kérd meg az AI-t: „Készíts nekem 5 darab exploratory testing charter-t a Boundary Value Analysis (határérték-elemzés) és a Error Guessing (hibatalálgatás) heurisztikái alapján erre a funkcióra.”
      • Segít feltárni olyan logikai vakfoltokat, amikre fejlesztőként vagy tesztelőként nem gondoltál volna.

    3. Vizuális és Interaktív AI Eszközök

    • Applitools (Eyes):
      • Exploratory tesztelés közben a legnagyobb ellenség a vizuális hiba, ami elkerüli a figyelmet. Az Applitools AI-alapú vizuális motorja azonnal észleli a renderelési hibákat, elcsúszásokat bármilyen eszközön, miközben te szabadon kattintgatsz és tesztelsz.

    Mit érdemes választani?

    1. Ha gyorsan és olcsón akarsz egyedi megoldást: Használj LLM alapú szkripteket / open-source keretrendszereket (Stagehand vagy custom Playwright szkriptek AI integrációval), amikkel autonóm módon át tudod vizsgáltatni a felületet.
    2. Ha strukturált, emberi exploratory tesztelést végzel: Használj AI-t (Claude/GPT) ötletgenerálásra és kockázatelemzésre a tesztelés előtt (hová érdemes kaparni, milyen inputokat próbálj ki), majd a session végén rögzítsd a tapasztalatokat egy Test Management eszközben (pl. Xray vagy TestRail), ahol az AI összefoglalja a bug riportokat.
  • JMeter – 50.000 virtuális user

    JMeter – 50.000 virtuális user

    Megkérdeztem a Gemini-t, hogy megoldható-e, és ha igen, hogyan egy 50.000 useres performancia tesztelés az Apache JMeter-rel. A válasz itt következik:

    Igen, teljesen megoldható, de van egy kritikus technikai feltétel: egyetlen gépről (például egy laptopról vagy egyetlen virtuális gépről) nem lehet elindítani 50 000 egyidejű felhasználót, mert a hardveres korlátok (CPU, RAM, hálózati socketek kimerülése) miatt maga a tesztelő eszköz fog összeomlani. Ehhez elosztott (distributed) architektúrára vagy felhőalapú futtatásra lesz szükség.

    Így kezdenék neki lépésről lépésre:

    1. Célok és workload modell tisztázása

    • Pontosan mit jelent az 50 000 user? 50 000 egyidejűleg aktív felhasználó, akik folyamatosan kattintgatnak, vagy 50 000 egyidejű kapcsolat (session), de az aktivitásuk eloszlik?
    • Milyen a felhasználói útvonal (User Journey)? Nem mindegy, hogy csak a főoldalt hívják le (ez könnyű), vagy bejelentkeznek, keresnek, terméket raknak a kosárba és fizetnek (ez bonyolultabb, adatbázis- és tranzakcióigényes).
    • Milyen mérőszámokat vársz el? (pl. 95%-os válaszidő 2 másodperc alatt, 0% hibaarány mellett).

    2. A tesztinfrastruktúra felépítése (Distributed JMeter)

    Mivel egy átlagos JMeter worker gép maximum 1000–2000 szálat bír stabilan, 50 000 userhez 25-50 darab worker gépre lesz szükség:

    • Master-Worker (Slave) felállás: Egy központi JMeter Master irányítja a tesztet, és gyűjti az adatokat a worker gépekről (ezek lehetnek olcsó AWS EC2 instance-ok vagy Docker konténerek).
    • Alternatíva: Használhatsz dedikált terheléses felhőszolgáltatásokat is (pl. BlazeMeter), amelyek mögött ugyanez az elv fut, de nem kell manuálisan menedzselni a 30-40 szervert.

    3. A JMeter script megírása

    • Thread Group beállítások: A 50 000 szálat érdemes fokozatosan ráengedni a rendszerre (Ramp-up period, pl. 30–45 perc alatt), hogy látsd, hol kezd el lassulni a rendszer.
    • Valósághű viselkedés: Használj Think Time-ot (szüneteket a kérések között), különben a szkript robotként viselkedik, amit egy WAF (Web Application Firewall) vagy cache réteg azonnal blokkolhat.
    • Dinamikus adatok:CSV Data Set Config segítségével biztosítsd, hogy a felhasználók különböző nevekkel, jelszódal vagy tokenekkel jelentkezzenek be, ne ugyanazzal az egy fiókkal indítsa mind az 50k szál a kérést.

    4. Szerveroldali monitorozás bekötése (Monitoring)

    A JMeter csak azt mutatja meg, mit tapasztal a felhasználó (válaszidő, hiba), azt nem, hogy miért lassult be a szerver. Ehhez kell a backend monitoring:

    • Alkalmazásszerverek, adatbázisok, cache (Redis) CPU, RAM és I/O terhelésének figyelése (pl. Prometheus + Grafana vagy Datadog).
    • Adatbázis lassan futó lekérdezéseinek és connection pool telítettségének mérése.

    5. Tesztfutás és finomhangolás

    • Soha ne használd a JMeter GUI-t (grafikus felületet) terheléses teszt alatt! Csak parancssorból, non-gui módban futtasd a master gépet.
    • Fokozatosság elve: Ne 50 000 userrel kezdd. Először futtass le egy 1000 useres tesztet, javítsd ki az azonnal előjövő hibákat, majd skálázd fel 5000-re, 10 000-re, és így tovább, ambron eléred a célt.

    Milyen típusú rendszert (pl. webshop, REST API, banki backend) kell tesztelni, és milyen környezetben fut jelenleg?

    Egy REST API terheléses tesztelése 50 000 felhasználóval specifikus technikai kihívásokat rejt, amelyekre különösen figyelned kell a JMeter konfigurálása során:

    1. Hitelesítés és token-kezelés (Authentication)

    A REST API-k túlnyomó többsége token alapú (JWT vagy OAuth2).

    • Ne kérj le tokent minden egyes kérésnél: Ha mind az 50 000 szál a POST /api/login végponttal kezdi a tesztet, azzal azonnal túlterheled a hitelesítőszervert, még mielőtt a valódi API funkciókat tesztelnéd.
    • Megoldás: Előre generálj le egy token-készletet (pl. egy külön scripttel vagy adatbázis-lekérdezéssel), töltsd be egy CSV fájlba, és a JMeterben ezt olvastasd be. Így a felhasználók már eleve érvényes tokenekkel küldhetik a kéréseket.

    2. HTTP Request Defaults és Keep-Alive

    Magas koncurrencia esetén kritikus a hálózati kapcsolatok hatékonysága:

    • HTTP Request Defaults: Állítsd be a központi URL-t, portot és a Content-Type-ot (application/json), hogy ne kelljen minden egyes kérésnél megismételni.
    • Keep-Alive: A JMeter alapértelmezésben engedélyezi a Keep-Alive-ot. REST API-knál ez elengedhetetlen, mert így nem nyit és zár le TCP kapcsolatot minden egyes apró API híváshoz, ami drasztikusan csökkenti a hálózati terhelést a kliens oldalon, és valósághűbb képet ad a szerver teljesítményéről.

    3. Dinamikus adatok és JSON Extractor

    Egy REST API láncolt hívásokból áll (pl. létrehozol egy erőforrást POST-tal, majd annak az ID-jét lekéred GET-tel).

    • Használj JSON Extractor elemet a válaszokból (pl. létrehozott objektum ID-je, státusza) való adatkigyűjtésre.
    • Ezeket az értékeket mentsd el JMeter változókba (${id}), hogy a következő kérésben dinamikusan fel tudd használni.

    4. Payload (Adatcsomag) optimalizálás

    • Ha a tesztben nagy JSON payloadokat küldesz (pl. tömeges adatfeltöltés), kerüld a bonyolult, dinamikusan generált szövegeket a JMeter belső függvényeivel, mert az leterheli a JMeter saját CPU-ját.
    • A tesztadatokat (JSON törzseket) előre generált fájlokból (CSV vagy JSON file) olvasd be, így a worker gépek erőforrásai tisztán a hálózati kérésekre fognak fókuszálni.

    5. Válaszidő és aszinkronitás

    • Határozd meg, hogy az API-n belül vannak-e aszinkron műveletek (pl. üzenetssorba, RabbitMQ-ba vagy Kafka-ba küldött események). Ha igen, a válaszidő alacsony lehet (mert a szerver csak átvette az üzenetet), de a valódi feldolgozást a háttérben lévő worker service-eknek kell bírniuk, így a monitoringot kötelező kiterjeszteni a háttérfolyamatokra is.

    Szerintem elég jól megválaszolta a kérdést. Egy részével tisztában voltam, sejtettem, de akadt jó pár jó tanács is. Gondoltam megkérdezem, hogy JMeteren kívül milyen mást eszközt ajánlana ehhez.

    Ha a JMeter elosztott architektúrájának (a 25-50 worker gépnek a) manuális konfigurálása és karbantartása túl bonyolultnak tűnik, a modern teljesítménytesztelő eszköztárban kifejezetten REST API-khoz sokkal gördülékenyebb megoldások is léteznek:

    1. k6 (Grafana k6) – A legjobb választás REST API-khoz

    A k6 jelenleg a legnépszerűbb fejlesztőbarát alternatíva. C-ben íródott (Go runtime-mal), így nagyságrendekkel kevesebb memóriát és CPU-t fogyaszt, mint a Java-alapú JMeter.

    • Miért ideális 50k userhez? Egyetlen erősebb szerverről lényegesen több szálat (virtuális usert) tud futtatni, mint a JMeter, így kevesebb infrastruktúrára lesz szükséged.
    • Szkriptelés: JavaScriptben írod a teszteket, ami nagyon természetes a REST API JSON válaszok és dinamikus tokenek kezelésénél.
    • Integráció: Kiemelkedően jó Grafana, Prometheus és CI/CD (GitHub Actions, GitLab) támogatása van.

    2. Gatling – Ha elképesztő skálázhatóságra és szép riportokra van szükség

    A Gatling aszinkron (Netty-alapú) architektúrát használ, így nem rendel egy külön operációs rendszer szálat minden egyes felhasználóhoz.

    • Miért ideális 50k userhez? Brutálisan jó az erőforrás-kihasználása. Egyetlen közepes gépről képes több tízezer egyidejű kapcsolatot felépíteni és kezelni.
    • Szkriptelés: Scala, Java vagy Kotlin nyelven írhatsz szép, olvasható domain-specifikus kódot (DSL).
    • Riportok: Alapból nagyon részletes és vizuálisan tetszetős HTML riportokat generál a teszt végén.

    3. Locust – Ha Python-alapú veremből dolgozol

    A Locust egy nyílt forráskódú, Python-alapú tesztelőeszköz, ahol a felhasználói viselkedést tiszta Python kódban definiálod.

    • Miért ideális 50k userhez? Nagyon egyszerűen összefűzhető elosztott (distributed) módba (Master-Worker architektúra), és menet közben, a böngészős UI-ról is állítható a terhelés mértéke.
    • Előny: Ha a csapat már ismeri a Pythont, a tesztek megírása és karbantartása minimális időt vesz igénybe.

    Menedzselt felhős alternatívák (SaaS)

    Ha egyáltalán nem szeretnél szervereket bérelni és konfigurálni:

    • Grafana Cloud k6 / BlazeMeter / Loader.io / AWS Distributed Load Testing:Ezeknél a szolgáltatásoknál csak feltöltöd a k6, JMeter vagy saját konfigurációs scriptjeidet, kiválasztod a régiót és a 50 000 felhasználós terhelést, ők pedig a háttérben azonnal felhúzzák a szükséges infrastruktúrát, majd a teszt végén leállítják azt.

    Összegzés – Melyiket válaszd?

    • Ha kódból írnál teszteket és a legjobb erőforrás-kihasználást akarod minimalizált szerverköltséggel: k6 vagy Gatling.
    • Ha a csapat Pythonban erős: Locust.
    • Ha nulla infrastruktúra-üzemeltetést szeretnél: Grafana Cloud k6 vagy BlazeMeter.
  • Tipikus interjú kérdés: yield

    Mennyi mindent használunk, de igazából lehet, hogy fogalmunk sincs róla, hogy mire is jó pontosan, és mikor is érdemes használni. A miértről nem is beszélve…

    Mit mesél erről a Gemini?

    A .NET-ben (C#) a yield kulcsszó pontosan ugyanazt a célt szolgálja, mint a Pythonban: IEnumerable<T> vagy IAsyncEnumerable<T> típusú adatfolyamokat hozhatsz vele létre úgy, hogy a kód lusta kiértékeléssel (lazy evaluation), elemenként adja vissza az eredményeket.

    C#-ban két formája létezik:

    • yield return <érték>: Visszaad egy elemet, majd felfüggeszti a metódus futását a következő elem kéréséig.
    • yield break: Azonnal leállítja az iterációt (megszakítja a generálást).

    Mire jó C#-ban?

    1. Memóriatakarékosság nagy adathalmazoknál

    Ha nem List<T>-et adsz vissza, nem kell a teljes gyűjteményt lefoglalni a RAM-ban.

    C#

    using System;
    using System.Collections.Generic;
    
    // PÉLDA 1: Hagyományos megközelítés (Memóriatakaréktalan)
    public List<int> GetNumbersList()
    {
        var list = new List<int>();
        for (int i = 0; i < 1_000_000; i++)
        {
            list.Add(i); // Mind az 1 milló elem belekerül a RAM-ba
        }
        return list;
    }
    
    // PÉLDA 2: yield használata (Memóriatakarékos)
    public IEnumerable<int> GetNumbersYield()
    {
        for (int i = 0; i < 1_000_000; i++)
        {
            yield return i; // Mindig csak az aktuális számot generálja le
        }
    }
    

    2. Szűrés és adatfeldolgozás yield break-kel

    A yield break használatával feltételhez kötheted az adatfolyam végét anélkül, hogy bonyolult feltételkezelést kellene írnod a ciklus köré:

    C#

    public IEnumerable<string> GetActiveUsers(List<string> users)
    {
        foreach (var user in users)
        {
            if (user == "ADMIN_STOP")
            {
                yield break; // Azonnal leállítja az elemek kiadását
            }
    
            if (!string.IsNullOrEmpty(user))
            {
                yield return user;
            }
        }
    }
    

    3. Óriási fájlok feldolgozása soronként

    Nem kell a gigabájtos fájlokat egyszerre a memóriába tölteni (File.ReadAllLines helyett):

    C#

    using System.IO;
    using System.Collections.Generic;
    
    public IEnumerable<string> ReadLargeFile(string filePath)
    {
        using (StreamReader reader = new StreamReader(filePath))
        {
            string? line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line; // Mindig csak egyetlen sort tart a memóriában
            }
        }
    }
    
    // Használat:
    foreach (string line in ReadLargeFile("huge_log.txt"))
    {
        if (line.Contains("ERROR"))
        {
            Console.WriteLine(line);
        }
    }
    

    4. Aszinkron adatfolyamok (C# 8.0+: IAsyncEnumerable<T>)

    Modern .NET-ben hálózati kéréseknél vagy adatbázis-lekérdezéseknél aszinkron módon is használhatod:

    C#

    public async IAsyncEnumerable<int> FetchDataAsync()
    {
        for (int i = 1; i <= 5; i++)
        {
            await Task.Delay(1000); // Pl. hálózati várakozás szimulálása
            yield return i;
        }
    }
    
    // Használat:
    await foreach (var item in FetchDataAsync())
    {
        Console.WriteLine(item); // Másodpercenként jelenik meg egy elem
    }
    

    Hagyományos visszatérés vs. yield C#-ban

    SzempontList<T> / Tömbyield return (IEnumerable<T>)
    Kiértékelés idejeAzonnali (A metódus hívásakor lefut a teljes ciklus)Lusta / Halasztott (Csak akkor fut, amikor a foreach kéri a következő elemet)
    MemóriahasználatMagas ($O(N)$ méretű lista a RAM-ban)Minimális ($O(1)$ állandó memóriagény)
    Sebesség (első elem)Lassú (meg kell várni a teljes lista felépítését)Azonnali (az első elem kérésére rögtön visszatér)

    Gondoltam, megkérdezem ugyanezt Typescript esetén.

    Igen, pontosan ugyanígy működik TypeScriptben és JavaScriptben is.

    A TypeScript a JavaScript generátor függvényeit (Generator Functions) használja, amelyeket az function* (csillaggal jelölt) szintaxisról lehet felismerni.

    A működési elv teljesen megegyezik a Python és a C# esetében látottakkal: a yield felfüggeszti a függvény futását, megőrzi a belső állapotot, és lusta kiértékeléssel (lazy evaluation) adja vissza az elemeket egy Generator objektumon keresztül.

    Alapvető példa TypeScriptben

    A generátor függvény visszatérési típusa Generator<T, TNext TReturn,>, ahol a T a yield-elt értékek típusa.

    TypeScript

    // A csillag (function*) jelzi, hogy ez egy generátor függvény
    function* szamGeneraltor(): Generator<number> {
      yield 1;
      yield 2;
      yield 3;
    }
    
    // Használat for...of ciklussal:
    for (const szam of szamGeneraltor()) {
      console.log(szam); // Kiírja egymás után: 1, 2, 3
    }
    

    Mire jó TypeScriptben?

    1. Memóriatakarékos és végtelen sorozatok

    Nem kell előre tömbbe gyűjteni az elemeket:

    TypeScript

    function* vegtelenAzonositoGeneral(): Generator<string> {
      let id = 1;
      while (true) {
        yield `ID_${id++}`;
      }
    }
    
    const idGen = vegtelenAzonositoGeneral();
    
    console.log(idGen.next().value); // "ID_1"
    console.log(idGen.next().value); // "ID_2"
    console.log(idGen.next().value); // "ID_3"
    

    2. Adatfolyamok delegálása (yield*)

    A TypeScriptben létezik a yield* utasítás is, amellyel átadhatod a vezérlést egy másik generátornak vagy tömbnek (bejárható objektumnak):

    TypeScript

    function* elsoResz(): Generator<number> {
      yield 1;
      yield 2;
    }
    
    function* teljesSorozat(): Generator<number> {
      yield* elsoResz(); // Delegálja az elemeket az elsoResz-ből
      yield* [3, 4, 5];  // Tömb elemeit is ki tudja adni egymás után
    }
    
    console.log([...teljesSorozat()]); // [1, 2, 3, 4, 5]
    

    3. Aszinkron generátorok (yield + async)

    Ahogy C#-ban az IAsyncEnumerable, TypeScriptben és JS-ben is létezik az AsyncGenerator (async function*). Ez hálózati adatok, lapozott API-k vagy adatfolyamok (stream) feldolgozásánál nagyon hasznos:

    TypeScript

    async function* adatokLetolteseAPIrol(): AsyncGenerator<string> {
      const oldalak = [1, 2, 3];
      
      for (const oldal of oldalak) {
        // Szimulált hálózati kérés (API hívás)
        const valasz = await fetch(`https://api.example.com/data?page=${oldal}`);
        const adat = await valasz.text();
        
        yield adat; // Mindig csak az aktuális oldal adatait adja vissza
      }
    }
    
    // Használat for await...of ciklussal:
    async function feldolgozas() {
      for await (const oldalAdat of adatokLetolteseAPIrol()) {
        console.log("Megérkezett egy oldal:", oldalAdat);
      }
    }
    

    Összehasonlítás

    NyelvFüggvény jelöléseKulcsszóBejárási felület
    C#IEnumerable<T> Get()yield returnforeach / LINQ
    Pythondef get():yieldfor item in gen
    TypeScriptfunction* get(): Generator<T>yieldfor...of / next()
  • Custom item list plugin AI-val generálva

    Tegnap egész sok mindennel kísérleteztem, majd eszembe jutott, hogy szerettem volna már egy ideje saját adatokat menteni a WordPress blog-omba, de sose volt elég időm, kedvem újra elővenni a PHP-t, és belekezdeni a fejlesztésbe. Szóval most, hogy van AI meg Gemini, megkértem, hogy csináljon egy alap plugin-t. Semmi extra, egy egyszerű CRUD, ahol a create, update, delete az admin felületen keresztül történik, a read pedig short code-ként kirakható bárhová.

    Azt azért látom, hogy ahhoz, ami a végső célom lenne, be kell húznom egy IDE-t, és abban prompt-olva a fejlesztést, de szerintem ez így elsőre nem tűnik rossznak.

    Az eredmény ez lett:

    NévURLTípus

    A generált kód pedig így néz ki:

    <?php
    /**
     * Plugin Name: Custom Items Manager
     * Description: CRUD bővítmény egyedi táblával, admin authentikációval és publikus szűrhető UI-val (Név, URL, Típus).
     * Version: 1.0
     * Author: Developer
     */
    
    if (!defined('ABSPATH')) {
        exit;
    }
    
    class CustomItemsManager {
        private $table_name;
    
        public function __construct() {
            global $wpdb;
            $this->table_name =$wpdb->prefix . 'custom_items';
    
            // Telepítéskor adatbázis tábla létrehozása
            register_activation_hook(__FILE__, array($this, 'activate'));
    
            // Admin menü létrehozása
            add_action('admin_menu', array($this, 'add_admin_menu'));
    
            // Shortcode a publikus felülethez
            add_shortcode('custom_items_list', array($this, 'render_public_ui'));
    
            // AJAX keresés végpontok (publikus és bejelentkezett felhasználóknak is)
            add_action('wp_ajax_filter_custom_items', array($this, 'ajax_filter_items'));
            add_action('wp_ajax_nopriv_filter_custom_items', array($this, 'ajax_filter_items'));
        }
    
        // Adatbázis tábla létrehozása
        public function activate() {
            global $wpdb;
            $charset_collate =$wpdb->get_charset_collate();
    
            $sql = "CREATE TABLE {$this->table_name} (             id INT(11) NOT NULL AUTO_INCREMENT,             name VARCHAR(255) NOT NULL,             url VARCHAR(255) NOT NULL,             type VARCHAR(100) NOT NULL,             created_at DATETIME DEFAULT CURRENT_TIMESTAMP,             PRIMARY KEY  (id)         )$charset_collate;";
    
            require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
            dbDelta($sql);
        }
    
        // Admin menü regisztrációja
        public function add_admin_menu() {
            add_menu_page(
                'Elemek Kezelése',
                'Elemek Kezelése',
                'manage_options', // Csak adminisztrátorok érhetik el
                'custom-items-manager',
                array($this, 'render_admin_page'),
                'dashicons-database',
                20
            );
        }
    
        // ADMIN UI: Create, Read, Update, Delete
        public function render_admin_page() {
            if (!current_user_can('manage_options')) {
                wp_die(__('Nincs jogosultságod az oldal megtekintéséhez.'));
            }
    
            global $wpdb;
            $message = '';
    
            // TÖRLÉS (Delete)
            if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
                $id = intval($_GET['id']);
                check_admin_referer('delete_item_' . $id);
                $wpdb->delete($this->table_name, array('id' => $id));$message = '<div class="updated"><p>Elem sikeresen törölve.</p></div>';
            }
    
            // MENTÉS / MÓDOSÍTÁS (Create / Update)
            if (isset($_POST['submit_item'])) {
                check_admin_referer('custom_item_form_nonce');
    
                $id = isset($_POST['item_id']) ? intval($_POST['item_id']) : 0;
                $name = sanitize_text_field($_POST['name']);
                $url = esc_url_raw($_POST['url']);
                $type = sanitize_text_field($_POST['type']);
    
                if (!empty($name) && !empty($url) && !empty($type)) {
                    if ($id > 0) {                     // Update$wpdb->update($this->table_name, array('name' =>$name, 'url' => $url, 'type' =>$type), array('id' => $id));$message = '<div class="updated"><p>Elem frissítve!</p></div>';
                    } else {
                        // Create
                        $wpdb->insert($this->table_name, array('name' => $name, 'url' =>$url, 'type' => $type));$message = '<div class="updated"><p>Új elem hozzáadva!</p></div>';
                    }
                } else {
                    $message = '<div class="error"><p>Minden mező kitöltése kötelező!</p></div>';
                }
            }
    
            // Szerkesztendő elem lekérése, ha van
            $edit_item = null;
            if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id'])) {
                $edit_item =$wpdb->get_row($wpdb->prepare("SELECT * FROM {$this->table_name} WHERE id = %d", intval($_GET['id'])));
            }
    
            // Összes elem lekérdezése az admin listához
            $items = $wpdb->get_results("SELECT * FROM {$this->table_name} ORDER BY id DESC");
    
            ?>
            <div class="wrap">
                <h1>Elemek Kezelése (Admin CRUD)</h1>
                <?php echo $message; ?>
    
                <!-- CRUD Űrlap (Create/Update) -->
                <h2><?php echo $edit_item ? 'Elem Szerkesztése' : 'Új Elem Hozzáadása'; ?></h2>
                <form method="post" style="background:#fff; padding:15px; border:1px solid #ccc; max-width:500px; margin-bottom:20px;">
                    <?php wp_nonce_field('custom_item_form_nonce'); ?>
                    <input type="hidden" name="item_id" value="<?php echo $edit_item ? $edit_item->id : 0; ?>">
                    
                    <p>
                        <label>Név:</label><br>
                        <input type="text" name="name" value="<?php echo $edit_item ? esc_attr($edit_item->name) : ''; ?>" required style="width:100%;">
                    </p>
                    <p>
                        <label>URL:</label><br>
                        <input type="url" name="url" value="<?php echo $edit_item ? esc_attr($edit_item->url) : ''; ?>" required style="width:100%;">
                    </p>
                    <p>
                        <label>Típus:</label><br>
                        <input type="text" name="type" value="<?php echo $edit_item ? esc_attr($edit_item->type) : ''; ?>" placeholder="pl. Blog, Bővítmény, Dokumentáció" required style="width:100%;">
                    </p>
                    <p>
                        <input type="submit" name="submit_item" class="button button-primary" value="<?php echo $edit_item ? 'Frissítés' : 'Mentés'; ?>">
                        <?php if ($edit_item): ?>
                            <a href="?page=custom-items-manager" class="button">Mégse</a>
                        <?php endif; ?>
                    </p>
                </form>
    
                <!-- Admin Lista (Read) -->
                <h2>Létező Elemek</h2>
                <table class="wp-list-table widefat fixed striped">
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Név</th>
                            <th>URL</th>
                            <th>Típus</th>
                            <th>Műveletek</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php if ($items): foreach ($items as$item): ?>
                            <tr>
                                <td><?php echo $item->id; ?></td>
                                <td><strong><?php echo esc_html($item->name); ?></strong></td>
                                <td><a href="<?php echo esc_url($item->url); ?>" target="_blank"><?php echo esc_html($item->url); ?></a></td>
                                <td><?php echo esc_html($item->type); ?></td>
                                <td>
                                    <a href="?page=custom-items-manager&action=edit&id=<?php echo $item->id; ?>" class="button button-small">Szerkesztés</a>
                                    <a href="<?php echo wp_nonce_url('?page=custom-items-manager&action=delete&id=' . $item->id, 'delete_item_' .$item->id); ?>" class="button button-small button-link-delete" onclick="return confirm('Biztosan törlöd?');">Törlés</a>
                                </td>
                            </tr>
                        <?php endforeach; else: ?>
                            <tr><td colspan="5">Nincs megjeleníthető elem.</td></tr>
                        <?php endif; ?>
                    </tbody>
                </table>
            </div>
            <?php
        }
    
        // PUBLIKUS UI: Shortcode kimenet és szűrő felület
        public function render_public_ui() {
            ob_start();
            ?>
            <div id="custom-items-public-container" style="max-width:800px; margin: 0 auto;">
                <!-- Szűrő Mezők -->
                <div style="display:flex; gap:10px; margin-bottom:20px; flex-wrap:wrap;">
                    <input type="text" id="filter-name" placeholder="Szűrés név alapján..." style="flex:1; padding:8px;">
                    <input type="text" id="filter-url" placeholder="Szűrés URL alapján..." style="flex:1; padding:8px;">
                    <input type="text" id="filter-type" placeholder="Szűrés típus alapján..." style="flex:1; padding:8px;">
                </div>
    
                <!-- Eredmények Táblázata -->
                <table style="width:100%; border-collapse:collapse; text-align:left;">
                    <thead>
                        <tr style="background:#f4f4f4; border-bottom:2px solid #ddd;">
                            <th style="padding:10px;">Név</th>
                            <th style="padding:10px;">URL</th>
                            <th style="padding:10px;">Típus</th>
                        </tr>
                    </thead>
                    <tbody id="custom-items-results">
                        <!-- AJAX tölti fel -->
                    </tbody>
                </table>
            </div>
    
            <script>
            document.addEventListener('DOMContentLoaded', function() {
                const nameInput = document.getElementById('filter-name');
                const urlInput = document.getElementById('filter-url');
                const typeInput = document.getElementById('filter-type');
                const resultsBody = document.getElementById('custom-items-results');
    
                function fetchItems() {
                    const params = new URLSearchParams({
                        action: 'filter_custom_items',
                        name: nameInput.value,
                        url: urlInput.value,
                        type: typeInput.value
                    });
    
                    fetch('<?php echo admin_url('admin-ajax.php'); ?>?' + params.toString())
                        .then(response => response.json())
                        .then(data => {
                            resultsBody.innerHTML = '';
                            if (data.length > 0) {
                                data.forEach(item => {
                                    const row = `
                                        <tr style="border-bottom:1px solid #eee;">
                                            <td style="padding:10px;"><strong>${escapeHtml(item.name)}</strong></td>
                                            <td style="padding:10px;"><a href="${escapeHtml(item.url)}" target="_blank" rel="noopener">${escapeHtml(item.url)}</a></td>
                                            <td style="padding:10px;">${escapeHtml(item.type)}</td>
                                        </tr>`;
                                    resultsBody.innerHTML += row;
                                });
                            } else {
                                resultsBody.innerHTML = '<tr><td colspan="3" style="padding:10px;">Nincs a szűrésnek megfelelő találat.</td></tr>';
                            }
                        });
                }
    
                function escapeHtml(text) {
                    return text.replace(/[&<>"']/g, function(m) {
                        return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m];
                    });
                }
    
                // Eseményfigyelők a dinamikus kereséshez
                nameInput.addEventListener('input', fetchItems);
                urlInput.addEventListener('input', fetchItems);
                typeInput.addEventListener('input', fetchItems);
    
                // Első betöltés
                fetchItems();
            });
            </script>
            <?php
            return ob_get_clean();
        }
    
        // AJAX Szűrés Lekérdezése
        public function ajax_filter_items() {
            global $wpdb;
    
            $name = isset($_GET['name']) ? sanitize_text_field($_GET['name']) : '';
            $url = isset($_GET['url']) ? sanitize_text_field($_GET['url']) : '';$type = isset($_GET['type']) ? sanitize_text_field($_GET['type']) : '';
    
            $query = "SELECT name, url, type FROM {$this->table_name} WHERE 1=1";
            $args = array();
    
            if (!empty($name)) {$query .= " AND name LIKE %s";
                $args[] = '%' . $wpdb->esc_like($name) . '%';
            }
            if (!empty($url)) {$query .= " AND url LIKE %s";
                $args[] = '%' . $wpdb->esc_like($url) . '%';
            }
            if (!empty($type)) {$query .= " AND type LIKE %s";
                $args[] = '%' . $wpdb->esc_like($type) . '%';
            }
    
            $query .= " ORDER BY name ASC";
    
            if (!empty($args)) {$results = $wpdb->get_results($wpdb->prepare($query,$args));
            } else {
                $results = $wpdb->get_results($query);
            }
    
            wp_send_json($results);
        }
    }
    
    new CustomItemsManager();
  • Névnapok plugin Gemini-vel generálva

    Gondoltam egy merészet, és megkértem a Gemini-t, hogy készítsen nekem egy WordPress Plugint, ami random Mészáros Lőrinc idézeteket jelenít meg. Megcsinálta.

    “Az élet egészen szélsőséges dolgokat is hozhat.”— Mészáros Lőrinc

    Nem álltam itt meg, megkértem, hogy csináljon egy névnap megjelenítőt, az aktuálisra, és a soron következőre.

    Ma: Nárcisz
    Holnap: Alfonz

    Íme:

    Itt egy komplett, egyfájlos WordPress bővítmény, amelyet közvetlenül beépíthetsz a weboldaladba. A bővítmény a látogató böngészőjének aktuális dátumát veszi alapul, kezeli a szökőéveket (a februári névnapeltolódást is), és shortcode segítségével bárhova könnyen elhelyezhető.

    Bővítmény kóda

    Hozz létre egy magyar-nevnapok.php fájlt a /wp-content/plugins/magyar-nevnapok/ mappában, és másold bele az alábbi kódot:

    PHP

    <?php
    /**
     * Plugin Name: Magyar Névnapok (Mai és Holnapi)
     * Description: Megjeleníti a mai és a holnapi névnapot shortcode segítségével.
     * Version: 1.0
     * Author: AI Assistant
     */
    
    if (!defined('ABSPATH')) {
        exit; // Közvetlen hozzáférés tiltása
    }
    
    class Magyar_Nevnapok_Plugin {
    
        // Névnap adatbázis (Hónap -> Nap -> Névnapok)
        private static $nevnapok = [
            1 => [
                1 => 'Fruzsina', 2 => 'Ábel', 3 => 'Genovéva, Benjámin', 4 => 'Titusz, Leona', 5 => 'Simon',
                6 => 'Boldizsár', 7 => 'Attila, Ramóna', 8 => 'Gyöngyvér', 9 => 'Marcell', 10 => 'Melánia',
                11 => 'Ágota', 12 => 'Ernő', 13 => 'Veronika', 14 => 'Bódog', 15 => 'Lóránt, Lóránd',
                16 => 'Gusztáv', 17 => 'Antal, Antónia', 18 => 'Piroska', 19 => 'Sára, Márió', 20 => 'Fábián, Sebestyén',
                21 => 'Ágnes', 22 => 'Vince, Artúr', 23 => 'Zelma, Rajmund', 24 => 'Timót', 25 => 'Pál',
                26 => 'Vanda, Paula', 27 => 'Angéla, Anzelm', 28 => 'Károly, Karola', 29 => 'Adél', 30 => 'Martina, Gerda',
                31 => 'Marcella'
            ],
            2 => [
                1 => 'Ignác', 2 => 'Karolina, Aida', 3 => 'Balázs', 4 => 'Ráhel, Csenge', 5 => 'Ágota, Ingrid',
                6 => 'Dorottya, Dóra', 7 => 'Tódor, Rómeó', 8 => 'Aranka', 9 => 'Abigél, Alex', 10 => 'Elvira',
                11 => 'Bertold, Marietta', 12 => 'Lívia, Lídia', 13 => 'Ella, Linda', 14 => 'Bálint, Valentin', 15 => 'Kolos, Georgina',
                16 => 'Julianna, Lilla', 17 => 'Donát', 18 => 'Bernadett', 19 => 'Zsuzsanna', 20 => 'Aladár, Álmos',
                21 => 'Eleonóra', 22 => 'Gerzson', 23 => 'Alfréd', 24 => 'Mátyás', 25 => 'Géza',
                26 => 'Edina', 27 => 'Ákos, Bátor', 28 => 'Elemér', 29 => 'Névnap szökőévben'
            ],
            3 => [
                1 => 'Albin', 2 => 'Lujza', 3 => 'Kornélia', 4 => 'Kázmér', 5 => 'Adorján, Adrián',
                6 => 'Leonóra, Inez', 7 => 'Tamás', 8 => 'Zoltán', 9 => 'Franciska, Fanni', 10 => 'Ildikó',
                11 => 'Szilárd', 12 => 'Gergely', 13 => 'Krisztián, Ajtony', 14 => 'Matild', 15 => 'Kristóf',
                16 => 'Henrietta', 17 => 'Patrick, Gertrúd', 18 => 'Ede, Sándor', 19 => 'József, Bánk', 20 => 'Klaudia',
                21 => 'Benedek', 22 => 'Beáta, Izolda', 23 => 'Emőke', 24 => 'Gábor, Karina', 25 => 'Irén, Írisz',
                26 => 'Emánuel', 27 => 'Hajnalka', 28 => 'Gedeon, Johanna', 29 => 'Auguszta', 30 => 'Zalán',
                31 => 'Árpád'
            ],
            4 => [
                1 => 'Hugó', 2 => 'Áron', 3 => 'Buda, Richárd', 4 => 'Izidor', 5 => 'Vince',
                6 => 'Vilmos, Bíbor', 7 => 'Herman', 8 => 'Dénes', 9 => 'Erhard', 10 => 'Zsolt',
                11 => 'Leó, Szaniszló', 12 => 'Gyula', 13 => 'Ida', 14 => 'Tibor', 15 => 'Anasztázia, Tas',
                16 => 'Csongor', 17 => 'Rudolf', 18 => 'Andrea, Ilma', 19 => 'Emma', 20 => 'Tivadar',
                21 => 'Konrád', 22 => 'Csilla, Noémi', 23 => 'Béla', 24 => 'György', 25 => 'Márk',
                26 => 'Ervin', 27 => 'Zita', 28 => 'Valéria', 29 => 'Péter', 30 => 'Katalin, Kitti'
            ],
            5 => [
                1 => 'Fülöp, Jakab', 2 => 'Zsigmond', 3 => 'Tímea, Irma', 4 => 'Mónika, Flórián', 5 => 'Györgyi',
                6 => 'Ivett, Frida', 7 => 'Gizella', 8 => 'Mihály', 9 => 'Gergely', 10 => 'Ármin, Pálmira',
                11 => 'Ferenc', 12 => 'Pongrác', 13 => 'Szervác, Imelda', 14 => 'Bonifác', 15 => 'Zsófia, Szonja',
                16 => 'Mózes, Botond', 17 => 'Paszkál', 18 => 'Erik, Alexandra', 19 => 'Ivó, Milán', 20 => 'Bernát, Felícia',
                21 => 'Konstantin', 22 => 'Júlia, Rita', 23 => 'Dezső', 24 => 'Eszter, Eliza', 25 => 'Orbán',
                26 => 'Fülöp, Evelin', 27 => 'Hella', 28 => 'Emil, Csanád', 29 => 'Magdolna', 30 => 'Janka, Zsanett',
                31 => 'Angéla, Petronella'
            ],
            6 => [
                1 => 'Tünde', 2 => 'Kármen, Anita', 3 => 'Klotild, Cecília', 4 => 'Bulcsú', 5 => 'Fatime, Fatima',
                6 => 'Norbert, Cintia', 7 => 'Róbert', 8 => 'Medárd', 9 => 'Félix', 10 => 'Margit, Gréta',
                11 => 'Barnabás', 12 => 'Villő', 13 => 'Antal, Anett', 14 => 'Vazul', 15 => 'Jolán, Vid',
                16 => 'Jusztin', 17 => 'Laura, Alida', 18 => 'Arnold, Levente', 19 => 'Gyárfás', 20 => 'Rafael',
                21 => 'Alajos, Leila', 22 => 'Paulina', 23 => 'Zoltán', 24 => 'Iván', 25 => 'Vilmos',
                26 => 'János, Pál', 27 => 'László', 28 => 'Levente, Irén', 29 => 'Péter, Pál', 30 => 'Pál'
            ],
            7 => [
                1 => 'Tihamér, Annamária', 2 => 'Ottó', 3 => 'Kornél, Soma', 4 => 'Ulrik', 5 => 'Emese, Sarolta',
                6 => 'Csaba', 7 => 'Apollónia', 8 => 'Ellák', 9 => 'Lukrécia', 10 => 'Amália',
                11 => 'Nóra, Lili', 12 => 'Izabella, Dalma', 13 => 'Jenő', 14 => 'Örs, Stella', 15 => 'Henrik, Roland',
                16 => 'Vali', 17 => 'Endre, Elek', 18 => 'Frigyes', 19 => 'Emília', 20 => 'Illés',
                21 => 'Dániel, Daniella', 22 => 'Magdolna', 23 => 'Lenke', 24 => 'Kinga, Kincső', 25 => 'Kristóf, Jakab',
                26 => 'Anna, Anikó', 27 => 'Olga, Liliána', 28 => 'Szabolcs', 29 => 'Márta, Flóra', 30 => 'Judit, Xénia',
                31 => 'Oszkár'
            ],
            8 => [
                1 => 'Boglárka', 2 => 'Lehel', 3 => 'Hermina', 4 => 'Domonkos, Dominika', 5 => 'Krisztina',
                6 => 'Berta, Bettina', 7 => 'Ibolya', 8 => 'László', 9 => 'Emőd', 10 => 'Lőrinc',
                11 => 'Zsuzsanna, Tiborc', 12 => 'Klára', 13 => 'Ipoly', 14 => 'Marcell', 15 => 'Mária',
                16 => 'Ábrahám', 17 => 'Jácint', 18 => 'Ilona', 19 => 'Huba', 20 => 'István',
                21 => 'Sámuel, Hajna', 22 => 'Menyhért', 23 => 'Bence', 24 => 'Bertalan', 25 => 'Lajos, Patrícia',
                26 => 'Izsó', 27 => 'Gáspár', 28 => 'Ágoston', 29 => 'Beatrix, Erna', 30 => 'Rózsa',
                31 => 'Erika, Bella'
            ],
            9 => [
                1 => 'Egyed, Egon', 2 => 'Rezső', 3 => 'Hilda', 4 => 'Rozália', 5 => 'Viktor, Lőrinc',
                6 => 'Zakariás', 7 => 'Regina', 8 => 'Mária, Adrienn', 9 => 'Ádám', 10 => 'Nikolett, Hunor',
                11 => 'Teodóra', 12 => 'Mária', 13 => 'Kornél', 14 => 'Szeréna, Roxána', 15 => 'Enikő, Melitta',
                16 => 'Edit', 17 => 'Zsófia', 18 => 'Diána', 19 => 'Vilhelmina', 20 => 'Friderika',
                21 => 'Máté, Mirella', 22 => 'Mór, Gellért', 23 => 'Tekla, Líviusz', 24 => 'Gellért, Mercédesz', 25 => 'Eufrozina, Kende',
                26 => 'Jusztina', 27 => 'Adalbert', 28 => 'Vencel', 29 => 'Mihály', 30 => 'Jeromos'
            ],
            10 => [
                1 => 'Malvin', 2 => 'Petra', 3 => 'Helga', 4 => 'Ferenc', 5 => 'Aurél',
                6 => 'Brúnó, Renáta', 7 => 'Amália', 8 => 'Koppány', 9 => 'Dénes', 10 => 'Gedeon',
                11 => 'Brigitta', 12 => 'Miksa', 13 => 'Kálmán, Ede', 14 => 'Helén', 15 => 'Teréz',
                16 => 'Gál', 17 => 'Hedvig', 18 => 'Lukács', 19 => 'Nándor', 20 => 'Vendel',
                21 => 'Orsolya', 22 => 'Előd', 23 => 'Gyöngyi', 24 => 'Salamon', 25 => 'Blanka, Bianca',
                26 => 'Dömötör', 27 => 'Szabina', 28 => 'Simon, Szimonetta', 29 => 'Nárcisz', 30 => 'Alfonz',
                31 => 'Farkas'
            ],
            11 => [
                1 => 'Marianna', 2 => 'Achilles', 3 => 'Győző', 4 => 'Károly', 5 => 'Imre',
                6 => 'Lénárd', 7 => 'Rezső', 8 => 'Zsombor', 9 => 'Tivadar', 10 => 'Réka',
                11 => 'Márton', 12 => 'Jónás, Renátó', 13 => 'Szilvia', 14 => 'Aliz', 15 => 'Albert, Lipót',
                16 => 'Ödön', 17 => 'Hortenzia, Gergő', 18 => 'Jenő', 19 => 'Erzsébet', 20 => 'Jolán',
                21 => 'Olivér', 22 => 'Cecília', 23 => 'Klementina', 24 => 'Emma', 25 => 'Katalin',
                26 => 'Virág', 27 => 'Virgil', 28 => 'Stefánia', 29 => 'Taksony', 30 => 'András, Andor'
            ],
            12 => [
                1 => 'Elza', 2 => 'Melinda, Vivien', 3 => 'Ferenc, Olívia', 4 => 'Borbála, Barbara', 5 => 'Vilma',
                6 => 'Miklós', 7 => 'Ambrus', 8 => 'Mária', 9 => 'Natália', 10 => 'Judit',
                11 => 'Árpád', 12 => 'Gabriella', 13 => 'Luca, Otília', 14 => 'Szilárda', 15 => 'Valér',
                16 => 'Etelka, Aletta', 17 => 'Lázár, Olimpia', 18 => 'Auguszta', 19 => 'Viola', 20 => 'Teofil',
                21 => 'Tamás', 22 => 'Zénó', 23 => 'Viktória', 24 => 'Ádám, Éva', 25 => 'Eugénia',
                26 => 'István', 27 => 'János', 28 => 'Kamilla', 29 => 'Tamás, Szeréna', 30 => 'Dávid',
                31 => 'Szilveszter'
            ]
        ];
    
        public function __construct() {
            add_shortcode('nevnapok', [$this, 'render_shortcode']);
        }
    
        /**
         * Névnap lekérése adott dátumra (kezeli a szökőéveket)
         */
        private function get_nevnap_by_timestamp($timestamp) {
            $month = (int)date('n', $timestamp);
            $day = (int)date('j', $timestamp);
            $is_leap_year = (date('L', $timestamp) == 1);
    
            // Szökőév esetén február 24. után a névnapok 1 nappal eltolódnak
            if ($is_leap_year && $month == 2) {
                if ($day == 24) {
                    return 'Szökőnap (Nincs külön névnap)';
                } elseif ($day > 24) {
                    $day--; // Február 25-29. a 24-28-ai névnapokat kapja
                }
            }
    
            return isset(self::$nevnapok[$month][$day]) ? self::$nevnapok[$month][$day] : 'Ismeretlen';
        }
    
        /**
         * Shortcode megjelenítése: [nevnapok]
         */
        public function render_shortcode($atts) {
            // Alapértelmezett beállítások felülírhatósága
            $atts = shortcode_atts([
                'mai_label'     => 'Mai névnap:',
                'holnapi_label' => 'Holnapi névnap:',
                'layout'        => 'block' // 'block' vagy 'inline'
            ], $atts, 'nevnapok');
    
            // WordPress időzóna beállítása alapján kérjük le az időt
            $now = current_time('timestamp');
            $tomorrow = strtotime('+1 day', $now);
    
            $mai_nevnap = $this->get_nevnap_by_timestamp($now);
            $holnapi_nevnap = $this->get_nevnap_by_timestamp($tomorrow);
    
            // HTML kimenet előállítása
            ob_start();
            ?>
            <div class="magyar-nevnapok-container layout-<?php echo esc_attr($atts['layout']); ?>">
                <div class="nevnap-item nevnap-mai">
                    <span class="nevnap-label"><?php echo esc_html($atts['mai_label']); ?></span>
                    <span class="nevnap-name"><?php echo esc_html($mai_nevnap); ?></span>
                </div>
                <div class="nevnap-item nevnap-holnapi">
                    <span class="nevnap-label"><?php echo esc_html($atts['holnapi_label']); ?></span>
                    <span class="nevnap-name"><?php echo esc_html($holnapi_nevnap); ?></span>
                </div>
            </div>
            <style>
                .magyar-nevnapok-container.layout-inline {
                    display: flex;
                    gap: 15px;
                    align-items: center;
                }
                .magyar-nevnapok-container .nevnap-label {
                    font-weight: bold;
                    margin-right: 4px;
                }
            </style>
            <?php
            return ob_get_clean();
        }
    }
    
    // Bővítmény inicializálása
    new Magyar_Nevnapok_Plugin();
    

    Használati útmutató

    1. Telepítés:
      • Hozz létre egy magyar-nevnapok nevű mappát a weboldalad wp-content/plugins/ könyvtárában.
      • Mentsd el a fenti kódot magyar-nevnapok.php néven ebbe a mappába.
      • Lépj be a WordPress admin felületére, és kapcsold be a Magyar Névnapok (Mai és Holnapi) bővítményt.
    2. Megjelenítés Shortcode-csal:Bármelyik oldalra, bejegyzésbe, widgetsávba vagy fejlécbe beillesztheted az alábbi shortcode-ok egyikét:
      • Alapértelmezett (blokk elrendezés):[ nevnapok ]
      • Egymás melletti (egysoros) elrendezés:[ nevnapok layout="inline" ]
      • Egyedi feliratok használata:[ nevnapok mai_label="Ma:" holnapi_label="Holnap:" layout="inline" ]

    1.Fájl létrehozása:

    Hozd létre a magyar-nevnapok.php fájlt a wp-content/plugins/magyar-nevnapok/ könyvtárban.

    2.Aktiválás:

    Lépj be a WordPress Adminba -> Bővítmények, majd aktiváld a „Magyar Névnapok” nevű bővítményt.

    3.Shortcode elhelyezése:

    Szúrd be a [ nevnapok ] shortcode-ot tetszőleges oldalba, blokkba vagy widgetbe.

  • Apache JMeter és a Throughput Controller

    Mivel napo óta ezzel szórakoztatom magam, gondoltam íratok erről pár sort az AI-val, mint emlékeztető, ha újra elő kell vennem a témát. Röviden: vannak esetek, amikor százalékos arányban kell hívásokat eszközölni a performancia mérésben. Ebben tud segíteni a Throughput Controller.

    Nézzük, mint mond erről az AI:

    A Throughput Controller az Apache JMeter egyik legfontosabb eleme, amikor a valós felhasználói viselkedést szeretnénk szimulálni. Neve ellenére nem a másodpercenkénti kérések számát (RPS) korlátozza (arra a Constant Throughput Timer való), hanem azt határozza meg, hogy a teszt lépései milyen arányban vagy rögzített alkalommal fussanak le.

    Mire jó a Throughput Controller?

    Egy webáruháznál a látogatók nem egyforma arányban böngésznek és vásárolnak:

    • 80% csak böngészi a termékeket.
    • 15% kosárba tesz valamit.
    • 5% eljut a fizetésig.

    A Throughput Controller segítségével pontosan ezt a megoszlást képezhetjük le egyetlen Thread Group-on belül.

    A két fő működési mód (Based On)

    MódLeírásMikor használd?
    Percent ExecutionsA beállított százalékos arányban engedi lefutni a gyermek kéréseket a teszt során.Valós felhasználói eloszlás szimulálásához (pl. 80-15-5% szabály).
    Total ExecutionsLegfeljebb egy megadott fix alkalommal engedi lefutni a kérést, utána kihagyja.Egyszeri beállításokhoz vagy korlátozott műveletekhez (pl. adatbázis-feltöltés).

    Fontos beállítás (Per User): Ha be van pipálva, a számítás nem a teljes tesztre, hanem szálanként (felhasználónként) érvényesül.

    Gyakorlati példa: Webáruház forgalomszimuláció

    Tételezzük fel, hogy 100 szálat (felhasználót) indítunk egy hurokban (Loop Count: 100). A célunk a fent említett 80%-15%-5%-os megoszlás.

    Struktúra a JMeterben:

    Plaintext

    Test Plan
     └── Thread Group (100 Threads, Loop: 10)
          ├── Throughput Controller (Percent Executions: 80.0)
          │    └── HTTP Request: Termékoldal megtekintése
          ├── Throughput Controller (Percent Executions: 15.0)
          │    └── HTTP Request: Kosárba helyezés
          └── Throughput Controller (Percent Executions: 5.0)
               └── HTTP Request: Fizetés
    

    Működés:

    • A 1000 összes lefutásból nagyjából 800 alkalommal a termékoldal kérés fog lefutni.
    • 150 alkalommal a kosárba helyezés.
    • 50 alkalommal pedig a fizetés lép fel.

    Gyakori hibák és tippek

    1. Ne keverd a Constant Throughput Timer-rel: Ha az a célod, hogy a szervert pontosan 100 req/sec terhelés alatt tartsd, használj Timert. A Throughput Controller az arányokról szól, nem a sebességről.
    2. Keresztülfutás (Overhead): Ha a szabályok összege nem 100%, vagy egy szál egyik controllernek sem felel meg az adott iterációban, a szál kihagyja az adott részt és lép a következő hurokra.
    3. Egymásba ágyazás: Összetettebb scenárióknál Simple Controller-rel vagy Transaction Controller-rel kombinálva egész folyamatokat is elhelyezhetsz egy-egy Throughput Controller alá.
  • POM avagy Page Object Model

    Mire jó az AI? Hát például arra, hogy ha írni szeretnék egy témáról, de lusta vagyok, az egyébként is Google segítsével összeszedett információkat rendszerbe szedni, akkor relatív gyorsan kézhez kapok egy bejegyzést.

    A Page Object Model (POM) a szoftvertesztelés-automatizálás egyik legnépszerűbb és leghatékonyabb tervezési mintája (Design Pattern). A lényege, hogy az alkalmazás egyes weboldalait (vagy oldalrészeit) különálló objektumokként képezi le a kód szintjén.

    Miért érdemes használni?

    • Karbantarthatóság: Ha megváltozik egy gomb ID-ja vagy egy mező elhelyezkedése, elegendő azt egyetlen helyen (a Page Object osztályban) átírni, a teszteseteket nem kell bántani.
    • Kód újrafelhasználhatósága: A gyakori műveletek (pl. bejelentkezés, keresés) egyszer vannak megírva, és bármelyik tesztből meghívhatók.
    • Olvashatóság: A tesztkód tiszta, átlátható és üzleti logikát követő lesz, rejtve a techniai megvalósítást (pl. DOM-lekérdezéseket).

    Minta megvalósítás (Python + Selenium)

    Az alábbiakban egy klasszikus bejelentkezési folyamat példáján keresztül látható a POM működése.

    A Page Object osztály (login_page.py)

    Ez az osztály tartalmazza az oldal elemeinek azonosítóit (lokátorait) és a végezhető műveleteket.

    Python

    from selenium.webdriver.common.by import By
    
    class LoginPage:
        def __init__(self, driver):
            self.driver = driver
            
            # Lokátorok (különválasztva a logikától)
            self._username_input = (By.ID, "username")
            self._password_input = (By.ID, "password")
            self._login_button = (By.CSS_SELECTOR, "button[type='submit']")
            self._error_message = (By.CLASS_NAME, "flash-error")
    
        # Akciók / Műveletek
        def enter_username(self, username: str):
            self.driver.find_element(*self._username_input).send_keys(username)
    
        def enter_password(self, password: str):
            self.driver.find_element(*self._password_input).send_keys(password)
    
        def click_login(self):
            self.driver.find_element(*self._login_button).click()
    
        # Összetett folyamat (Láncolt / Magasabb szintű akció)
        def login(self, username: str, password: str):
            self.enter_username(username)
            self.enter_password(password)
            self.click_login()
    
        def get_error_message(self) -> str:
            return self.driver.find_element(*self._error_message).text
    

    A Teszteset (test_login.py)

    A tesztkód csak a Page Object által nyújtott metódusokat használja, így végtelenül egyszerű és olvasható.

    Python

    import pytest
    from selenium import webdriver
    from login_page import LoginPage
    
    @pytest.fixture
    def driver():
        driver = webdriver.Chrome()
        driver.get("https://example.com/login")
        yield driver
        driver.quit()
    
    def test_successful_login(driver):
        login_page = LoginPage(driver)
        
        # Bejelentkezés végrehajtása
        login_page.login("valid_user", "correct_password")
        
        # Ellenőrzés (Assertion)
        assert driver.current_url == "https://example.com/dashboard"
    
    def test_failed_login(driver):
        login_page = LoginPage(driver)
        
        login_page.login("invalid_user", "wrong_password")
        
        # Hibaüzenet ellenőrzése
        assert "Érvénytelen adatok" in login_page.get_error_message()
    

    Jó tanácsok a gyakorlati alkalmazáshoz

    • Ne tegyél assertion-öket a Page Object-be: A assert kifejezések a tesztfájlban maradjanak, a Page Object csak adatot adjon vissza vagy állapotot módosítson.
    • Page Factory használata: Bizonyos keretrendszerekben (pl. Java + Selenium) a @FindBy annotációkkal még tisztább szerkezet érhető el.
    • Lazy loading: Az elemeket csak akkor kérd le a DOM-ból, amikor ténylegesen szükség van rájuk (például dinamikus felületek esetén).

    A Page Object Model (POM) megvalósítása a modernebb eszközökkel (mint a Playwright vagy a Selenium Java-alapú változata) még letisztultabbá válik, mivel ezek a keretrendszerek beépített architektúrával támogatják a megbízhatóbb elemkezelést.

    Az alábbiakban mindkét megközelítésre találsz egy-egy komplett kódmintát.

    Playwright + TypeScript (Ajánlott / Modern megközelítés)

    A Playwright eleve magában foglalja az automatikus várakozást (Auto-waiting), így nincs szükség explicit wait-ek kézi kezelésére.

    A Page Object osztály (pages/LoginPage.ts)

    TypeScript

    import { Page, Locator } from '@playwright/test';
    
    export class LoginPage {
      readonly page: Page;
      readonly usernameInput: Locator;
      readonly passwordInput: Locator;
      readonly loginButton: Locator;
      readonly errorMessage: Locator;
    
      constructor(page: Page) {
        self.page = page;
        // A Playwright-ban a lokátorok deklaratívak, nem kérdezik le azonnal a DOM-ot
        this.usernameInput = page.locator('#username');
        this.passwordInput = page.locator('#password');
        this.loginButton = page.locator('button[type="submit"]');
        this.errorMessage = page.locator('.flash-error');
      }
    
      async goto() {
        await this.page.goto('https://example.com/login');
      }
    
      async login(username: string, password: string) {
        await this.usernameInput.fill(username);
        await this.passwordInput.fill(password);
        await this.loginButton.click();
      }
    
      async getErrorMessage(): Promise<string> {
        return await this.errorMessage.textContent() || '';
      }
    }
    

    A Tesztfájl (tests/login.spec.ts)

    TypeScript

    import { test, expect } from '@playwright/test';
    import { LoginPage } from '../pages/LoginPage';
    
    test.describe('Bejelentkezés tesztek', () => {
      let loginPage: LoginPage;
    
      test.beforeEach(async ({ page }) => {
        loginPage = new LoginPage(page);
        await loginPage.goto();
      });
    
      test('Sikeres bejelentkezés', async ({ page }) => {
        await loginPage.login('valid_user', 'correct_password');
        
        // Ellenőrzés Playwright assert-tel
        await expect(page).toHaveURL('https://example.com/dashboard');
      });
    
      test('Hibás bejelentkezés', async () => {
        await loginPage.login('invalid_user', 'wrong_password');
        
        await expect(loginPage.errorMessage).toBeVisible();
        await expect(loginPage.errorMessage).toContainText('Érvénytelen adatok');
      });
    });
    

    Java + Selenium (Klasszikus keretrendszer)

    Java környezetben a PageFactory és a @FindBy annotációk használata a legelterjedtebb POM minta.

    A Page Object osztály (LoginPage.java)

    Java

    package pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import org.openqa.selenium.support.PageFactory;
    
    public class LoginPage {
        private WebDriver driver;
    
        // Lokátorok deklarálása annotációkkal
        @FindBy(id = "username")
        private WebElement usernameInput;
    
        @FindBy(id = "password")
        private WebElement passwordInput;
    
        @FindBy(css = "button[type='submit']")
        private WebElement loginButton;
    
        @FindBy(className = "flash-error")
        private WebElement errorMessage;
    
        // Konstruktor: inicializálja a PageFactory elemeit
        public LoginPage(WebDriver driver) {
            this.driver = driver;
            PageFactory.initElements(driver, this);
        }
    
        public void enterUsername(String username) {
            usernameInput.clear();
            usernameInput.sendKeys(username);
        }
    
        public void enterPassword(String password) {
            passwordInput.clear();
            passwordInput.sendKeys(password);
        }
    
        public void clickLogin() {
            loginButton.click();
        }
    
        public void login(String username, String password) {
            enterUsername(username);
            enterPassword(password);
            clickLogin();
        }
    
        public String getErrorMessageText() {
            return errorMessage.getText();
        }
    }
    

    A Tesztosztály (JUnit 5 használatával) (LoginTest.java)

    Java

    package tests;
    
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import pages.LoginPage;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    public class LoginTest {
        private WebDriver driver;
        private LoginPage loginPage;
    
        @BeforeEach
        public void setUp() {
            driver = new ChromeDriver();
            driver.manage().window().maximize();
            driver.get("https://example.com/login");
            loginPage = new LoginPage(driver);
        }
    
        @Test
        public void testSuccessfulLogin() {
            loginPage.login("valid_user", "correct_password");
            assertEquals("https://example.com/dashboard", driver.getCurrentUrl());
        }
    
        @Test
        public void testFailedLogin() {
            loginPage.login("invalid_user", "wrong_password");
            assertTrue(loginPage.getErrorMessageText().contains("Érvénytelen adatok"));
        }
    
        @AfterEach
        public void tearDown() {
            if (driver != null) {
                driver.quit();
            }
        }
    }
    

    Főbb különbségek a két megközelítés között

    SzempontPlaywright (TypeScript)Selenium (Java)
    LokátorokA Locator objektumok deklaratívak (auto-wait)A @FindBy elemek lekezeléséhez várakozás (WebDriverWait) kellhet
    PárhuzamosításAlapból támogatott a tesztfutztatóbanTestNG vagy JUnit szálkezelést igényel
    InicializálásEgyszerű objektumkonstruktorPageFactory.initElements() hívás szükséges

    A Playwright Custom Fixture funkciója segítségével a Page Object osztályok példányosítását teljesen kiszervezheted a tesztekből. Ezzel elkerülhető, hogy minden egyes tesztfájlban manuálisan kelljen megadni a const loginPage = new LoginPage(page) sorokat.

    A tesztek így közvetlenül paraméterként kérhetik el a szükséges oldalobjektumokat, az infrastruktúra pedig automatikusan példányosítja és előkészíti azokat.

    A Page Object osztályok (pages/)

    A megszokott módon hozzuk létre az oldalklasszokat.

    TypeScript

    // pages/LoginPage.ts
    import { Page, Locator } from '@playwright/test';
    
    export class LoginPage {
      readonly page: Page;
      readonly usernameInput: Locator;
    
      constructor(page: Page) {
        this.page = page;
        this.usernameInput = page.locator('#username');
      }
    
      async goto() {
        await this.page.goto('/login');
      }
    }
    
    // pages/DashboardPage.ts
    import { Page, Locator } from '@playwright/test';
    
    export class DashboardPage {
      readonly page: Page;
      readonly userMenu: Locator;
    
      constructor(page: Page) {
        this.page = page;
        this.userMenu = page.locator('#user-menu');
      }
    }
    

    A Custom Fixture létrehozása (fixtures/baseTest.ts)

    Itt terjesztjük ki a Playwright alapértelmezett test objektumát, megadva a saját Page Object fixture-jeinket.

    TypeScript

    import { test as base } from '@playwright/test';
    import { LoginPage } from '../pages/LoginPage';
    import { DashboardPage } from '../pages/DashboardPage';
    
    // 1. Típusdefiníció a fixture-ökhöz
    type MyFixtures = {
      loginPage: LoginPage;
      dashboardPage: DashboardPage;
    };
    
    // 2. A 'base.extend' használatával regisztráljuk a saját fixture-jeinket
    export const test = base.extend<MyFixtures>({
      // Automatikusan példányosítja a LoginPage-t és igény esetén megnyitja az oldalt
      loginPage: async ({ page }, use) => {
        const loginPage = new LoginPage(page);
        await loginPage.goto(); // Akár előkészítő lépéseket is tehetünk ide
        await use(loginPage);   // Átadja az objektumot a tesztnek
      },
    
      // Példányosítja a DashboardPage-t
      dashboardPage: async ({ page }, use) => {
        const dashboardPage = new DashboardPage(page);
        await use(dashboardPage);
      },
    });
    
    // Az 'expect'-et is exportáljuk a kényelmes importálásért
    export { expect } from '@playwright/test';
    

    A Fixture-ök használata a tesztekben (tests/login.spec.ts)

    A @playwright/test helyett a saját fixtures/baseTest fájlunkból importáljuk a test objektumot. A tesztfüggvény paramétereként megadott nevek alapján a Playwright automatikusan beadagolja a megfelelő példányokat.

    TypeScript

    // Figyelem: A saját kiterjesztett test objektumunkat importáljuk!
    import { test, expect } from '../fixtures/baseTest';
    
    test('Sikeres bejelentkezés custom fixture-rel', async ({ loginPage, dashboardPage }) => {
      // A loginPage.goto() már lefutott a fixture-ben!
      await loginPage.usernameInput.fill('standard_user');
      
      // A dashboardPage is azonnal használható, nem kellett 'new'-val példányosítani
      await expect(dashboardPage.userMenu).toBeVisible();
    });
    

    Miért ez a legnépszerűbb Playwright architektúra?

    • Nulla boilerplate kód: Nem kell beforeEach blokkokban példányosítani az osztályokat.
    • Lazy loading (Lusta betöltés): Csak azok a Page Object-ek példányosulnak, amiket a teszt fejlécében ({ loginPage, dashboardPage }) kifejezetten elkérsz.
    • Beépített tear-down: A fixture-ben a use() hívás utáni kódrészlet automatikusan lefut a teszt végén (takarításra, kijelentkezésre ideális).
  • Legáltalánosabb GIT parancsok

    Legáltalánosabb GIT parancsok

    Sosem voltam egy nagy karakter húszár, így előnybe részesítettem a grafikus ezközöket. Mostanában viszont egyre gyakrabban kényszerűlők rá, hogy parancssorosan oldjak meg dolgok, mint pl a GIT-elést. Gondoltam AI-val összegyűjtetem a legáltalánosabb parancsokat.

    Infografika a Git alapvető parancsairól: inicializálás, módosítások követése, commit, ágkezelés és távoli tároló

    A leggyakrabban használt Git parancsok kategóriák szerint csoportosítva:

    Alapvető beállítások & Inicializálás

    • git config --global user.name "Neved" – Beállítja a felhasználóneved a commitokhoz.
    • git config --global user.email "email@cimed.hu" – Beállítja az e-mail-címedet.
    • git init – Új, üres Git repót hoz létre az aktuális mappában.
    • git clone <URL> – Letölt egy meglévő távoli tárolót (repository-t) a gépedre.

    Változások nyomon követése & Commit

    • git status – Megmutatja a munkamenet állapotát (módosított, követetlen fájlok).
    • git add <fájlnév> – Hozzáadja a megadott fájlt a Staging Area-hoz.
    • git add . – Az összes módosított és új fájlt hozzáadja a Staging Area-hoz.
    • git commit -m "Üzenet" – Elmenti a Staging Area-ban lévő változtatásokat a megadott üzenettel.
    • git diff – Megmutatja a még el nem mentett (unstaged) módosításokat.

    Nézet & Történet

    • git log – Megjeleníti a korábbi commitok történetét.
    • git log --oneline – Tömör, egysoros formátumban listázza a commitokat.

    Ágak kezelése (Branching)

    • git branch – Listázza a helyi ágakat.
    • git branch <ág-név> – Létrehoz egy új ágat.
    • git checkout <ág-név> – Átvált a megadott ágra.
    • git checkout -b <ág-név> – Létrehoz egy új ágat és egyből át is vált rá.
    • git merge <ág-név> – A megadott ág tartalmát összefésüli az aktuális ággal.

    Távoli tároló (Remote)

    • git fetch – Letölti az új információkat a távoli tárolóból, de nem egyesíti őket.
    • git pull – Letölti és azonnal összefésüli a távoli ág változásait.
    • git push origin <ág-név> – Feltölti a helyi commitokat a távoli tárolóba.

    Módosítások visszavonása

    • git restore <fájlnév> – Visszaállítja a fájlt a legutóbbi commit állapotára (eldobja a nem elmentett módosításokat).
    • git reset HEAD~1 – Visszavonja a legutóbbi commitot, de a fájlokban lévő módosításokat megőrzi.

    Git Parancsok Gyorsútmutató

    KategóriaParancsLeírás
    Beállítás & Kezdésgit config --global user.name "Neved"Felhasználónév beállítása
    git config --global user.email "email@cimed.hu"E-mail-cím beállítása
    git initÚj helyi repó inicializálása
    git clone <URL>Távoli tároló letöltése
    Változások Kezelésegit statusMunkamenet állapotának ellenőrzése
    git add <fájl> vagy git add .Fájl(ok) hozzáadása a Staging Area-hoz
    git commit -m "Üzenet"Változtatások véglegesítése üzenettel
    git diffNem elmentett módosítások megtekintése
    Történet & Ágakgit log --onelineCommit előzmények tömör listázása
    git branchHelyi ágak listázása
    git checkout -b <ág-név>Új ág létrehozása és átváltás
    git merge <ág-név>Ág összefésülése az aktuálissal
    Távoli Tároló (Remote)git pullFrissítések letöltése és összefésülése
    git push origin <ág-név>Helyi commitok feltöltése
    Módosítások Visszavonásagit restore <fájl>Nem elmentett módosítások eldobása
    git reset HEAD~1Legutóbbi commit visszavonása (fájlok megmaradnak)
  • Duplikációk keresése, avagy power shell script íratása AI-val

    Duplikációk keresése, avagy power shell script íratása AI-val

    Ma ilyen AI-s napom van. Egyre jobban tör be a mindennapi munkámba, és úgy tűnik, hogy a hétvégeimet is ez teszi ki. Miért? Kérdezheted joggal. Megmondom. Az IT-ban, de gyanítom a többi szakmában is jobb, ha értünk hozzá, és megtanuljuk rutinosan használni, mintsem lemaradjunk róla, és nézzük bambán, ahogy az élet felsuhan előttünk.

    Nézzük, mire is mentem elsőre.

    Az alábbi PowerShell-script végigpásztázza a megadott mappát (és annak almappáit), a fájlok SHA256 hash értéke alapján azonosítja a duplikációkat, majd az eredményt kiírja a képernyőre és elmenti egy CSV-fájlba.

    PowerShell duplikáció-kereső script

    Hozz létre egy find-duplicates.ps1 fájlt, és másold bele az alábbi kódot:

    PowerShell

    # --- BEÁLLÍTÁSOK ---
    $TargetFolder = "C:\Elérési\Út\A\Mappához"   # Cseréld ki a vizsgálandó mappa útvonalára
    $OutputFile   = "C:\Elérési\Út\duplikatumok.csv" # Ide menti az eredményt
    
    Write-Host "Fájlok indexelése és hash értékek számítása..." -ForegroundColor Cyan
    
    # 1. Összes fájl lekérése és méret szerinti csoportosítása (gyorsítási lépés)
    $fileGroups = Get-ChildItem -Path $TargetFolder -Recurse -File -ErrorAction SilentlyContinue |
        Group-Group-Object -Property Length |
        Where-Object { $_.Count -gt 1 }
    
    $duplicates = @()
    $totalGroups = $fileGroups.Count
    $counter = 0
    
    # 2. Csak az azonos méretű fájlok SHA256 hash-ének ellenőrzése
    foreach ($group in $fileGroups) {
        $counter++
        Write-Progress -Activity "Duplikátumok keresése" -Status "Fájlcsoportok feldolgozása: $counter / $totalGroups" -PercentComplete (($counter / $totalGroups) * 100)
    
        $hashTable = $group.Group | Get-FileHash -Algorithm SHA256 | Group-Object -Property Hash | Where-Object { $_.Count -gt 1 }
    
        foreach ($hashGroup in $hashTable) {
            foreach ($file in $hashGroup.Group) {
                $duplicates += [PSCustomObject]@{
                    Hash     = $hashGroup.Name
                    Path     = $file.Path
                    SizeMB   = [math]::Round((Get-Item $file.Path).Length / 1MB, 2)
                }
            }
        }
    }
    
    # 3. Eredmények megjelenítése és mentése
    if ($duplicates.Count -gt 0) {
        Write-Host "`nTalált duplikátumok:" -ForegroundColor Green
        $duplicates | Format-Table -AutoSize -Property Hash, SizeMB, Path
    
        $duplicates | Export-Csv -Path $OutputFile -NoTypeInformation -Encoding UTF8
        Write-Host "Az eredmények elmentve ide: $OutputFile" -ForegroundColor Yellow
    } else {
        Write-Host "`nNem találhatók duplikált fájlok." -ForegroundColor Green
    }
    

    Így használd:

    1. Nyisd meg a PowerShell-t (akár Rendszergazdaként, ha védett mappákat is vizsgálsz).
    2. Írd át a $TargetFolder változót a keresni kívánt mappára.
    3. Futtasd a scriptet:PowerShell.\find-duplicates.ps1

    Megjegyzés: Ha a PowerShell letiltja a scriptek futtatását, futtasd ezt a parancsot a munkamenetben:

    Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

    Majd második próbálkozásra

    Módosítottam a scriptet, hogy a célmappát (és opcióként a kimeneti CSV-fájl helyét is) paraméterként fogadja el.

    A módosított script kódja:

    PowerShell

    <#
    .SYNOPSIS
        Fájl duplikációkat kereső és gyűjtő PowerShell script.
    
    .DESCRIPTION
        A script végigpásztázza a paraméterként megadott mappát (és annak almappáit),
        és az SHA256 hash értékek alapján azonosítja a duplikátumokat.
        Az eredményt megjeleníti a konzolon és CSV-fájlba menti.
    
    .PARAMETER TargetFolder
        A vizsgálandó mappa útvonala (kötelező paraméter).
    
    .PARAMETER OutputFile
        A kimeneti CSV fájl útvonala (opcionális, alapértelmezett: .\duplikatumok.csv).
    #>
    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, HelpMessage = "Kérjük, adja meg a vizsgálandó mappa útvonalát!")]
        [ValidateScript({ Test-Path $_ -PathType Container })]
        [string]$TargetFolder,
    
        [Parameter(Mandatory = $false, Position = 1)]
        [string]$OutputFile = ".\duplikatumok.csv"
    )
    
    # Abszolút útvonalak feloldása
    $TargetFolder = (Resolve-Path $TargetFolder).Path
    $OutputFile   = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputFile)
    
    Write-Host "Mappa elemzése: $TargetFolder" -ForegroundColor Cyan
    Write-Host "Fájlok indexelése és méret szerinti csoportosítása..." -ForegroundColor Cyan
    
    # 1. Összes fájl lekérése és méret szerinti csoportosítása (gyorsítási lépés)
    $fileGroups = Get-ChildItem -Path $TargetFolder -Recurse -File -ErrorAction SilentlyContinue |
        Group-Object -Property Length |
        Where-Object { $_.Count -gt 1 }
    
    $duplicates = @()
    $totalGroups = $fileGroups.Count
    $counter = 0
    
    if ($totalGroups -eq 0) {
        Write-Host "`nNem találhatók azonos méretű fájlok, nincsenek duplikátumok." -ForegroundColor Green
        exit
    }
    
    # 2. Csak az azonos méretű fájlok SHA256 hash-ének ellenőrzése
    foreach ($group in $fileGroups) {
        $counter++
        Write-Progress -Activity "Duplikátumok keresése" -Status "Fájlcsoportok feldolgozása: $counter / $totalGroups" -PercentComplete (($counter / $totalGroups) * 100)
    
        $hashTable = $group.Group | Get-FileHash -Algorithm SHA256 | Group-Object -Property Hash | Where-Object { $_.Count -gt 1 }
    
        foreach ($hashGroup in $hashTable) {
            foreach ($file in $hashGroup.Group) {
                $duplicates += [PSCustomObject]@{
                    Hash     = $hashGroup.Name
                    Path     = $file.Path
                    SizeMB   = [math]::Round((Get-Item $file.Path).Length / 1MB, 2)
                }
            }
        }
    }
    
    # 3. Eredmények megjelenítése és mentése
    if ($duplicates.Count -gt 0) {
        Write-Host "`nTalált duplikátumok:" -ForegroundColor Green
        $duplicates | Format-Table -AutoSize -Property Hash, SizeMB, Path
    
        $duplicates | Export-Csv -Path $OutputFile -NoTypeInformation -Encoding UTF8
        Write-Host "Az eredmények elmentve ide: $OutputFile" -ForegroundColor Yellow
    } else {
        Write-Host "`nNem találhatók duplikált fájlok." -ForegroundColor Green
    }
    

    Használati példák:

    1. Alapértelmezett használat (a mappa elérést megadva, az eredmény a beállítások szerint a duplikatumok.csv-be kerül):PowerShell.\find-duplicates.ps1 -TargetFolder "C:\Users\Public\Documents"
    2. Pozicionális paraméterként (nem kell kiírni a névadó kapcsolót):PowerShell.\find-duplicates.ps1 "D:\Képek"
    3. Egyedi CSV kimeneti fájlnév megadásával:PowerShell.\find-duplicates.ps1 -TargetFolder "D:\Dokumentumok" -OutputFile "C:\Riportok\doksi_duplikatumok.csv"

    Újdonságok a scriptben:

    • Automatikus mappa-ellenőrzés (ValidateScript): Ha nem létező vagy hibás mappa-útvonalat adsz meg, a script még a futás előtt jelez és leáll.
    • Mappa-kötelezőség (Mandatory = $true): Ha paraméter nélkül indítod el a scriptet, a PowerShell automatikusan megkérdezi tőled a célmappa útvonalát.

    Nem vagyok teljes mértékben elragadtatva az eredménytől, de mára ez bőven elég volt, majd folytatjuk…