Aplikacje WWW

Table of Contents

Zajęcia 6-8

Krok 1: Uruchamiamy tsc

Aby móc korzystać z kompilatora TypeScriptu musimy go zainstalować. Wprawdzie mamy już jedną kopię załączoną do VS Code, ale potrzebna nam będzie jeszcze jedna, z której będziemy korzystali z linii poleceń.

Zanim zaczniemy trzeba sprawdzić, czy niczego nie popsuliśmy i działa nvm zainstalowany na pierwszych zajęciach:

nvm current

Jeśli nie wychodzi v10.15.1 albo coś podobnego, to trzeba poprawnie zainstalować nvm, patrz zajęcia pierwsze.

Kompilator zainstalujemy globalnie, żeby wygodniej było go używać; przy okazji dodamy program TSLint, który będzie wyszukiwał jeszcze więcej problemów z kodem. TSLint warto też dodać do VS Code, żeby informacje o błędach mieć na bieżąco.

npm install -g typescript tslint
code --install-extension "ms-vscode.vscode-typescript-tslint-plugin"

Po wpisaniu tych poleceń z konsoli powinniśmy móc uruchomić tsc i tslint, a w edytorze powinien być dodatek TSLint. Program tslint trzeba będzie skonfigurować do użycia z naszym kodem.


TypeScript:
Plus oczywiscie:

Wiecej informacji o nvm:


Krok 2: Tworzymy nowy projekt i piszemy pierwszy program

Będziemy do tego potrzebowali nowego katalogu i pomocy programu npm:

mkdir nowy_projekt
cd nowy_projekt
npm init -y  

W poważniejszych projektach warto wyedytować plik package.json tak, by zawierał rozsądne informacje, ale do naszych potrzeb nie jest to niezbędne.

Otwieramy katalog zawierający package.json w VS Code. Do pierwszych eksperymentów stwórzmy nową stronę (plik .html), np. korzystając z szablonu w VS Code. Stwórzmy plik main.css, początkowo pusty, ale zamiast pliku main.js stwórzmy main.ts. W pliku main.ts napiszmy:

console.log("Ależ skomplikowany program!");

Niestety, gdy otworzymy plik .html w przeglądarce i otworzymy konsolę w narzędziach deweloperskich, zobaczymy, że nie udało się załadować pliku main.js. Musimy więc stworzyć taki plik, jako wynik kompilacji main.ts. Na konsoli piszemy:

tsc main.ts

W katalogu pojawia się plik main.js, identyczny z main.ts, a po przeładowaniu strony konsola wyświetla komunikat. Udało się nam uruchomić pierwszy program w TypeScripcie!

Zobaczmy jeszcze co o tym programie myśli TSLint. Najpierw musimy stworzyć domyślną konfigurację:

tslint -i

a potem wywołać go na naszym pliku:

tslint main.ts

TSLint w domyślnej konfiguracji nie lubi programów korzystających z konsoli. Widać to też w edytorze - napis console.log jest podkreślony, a okienko z listą błędów zawiera komunikat wypisywany przez TSLint. Spróbujmy go jednak przekonać, że konsola jest fajna. Dopiszmy do tslint.json linijkę:

"rules": {
    "no-console": false
},

I już możemy się cieszyć brakiem błędów zgłaszanych przez program. Dobrze jest jednak pamiętać, że zwykle wyłączenie komunikatu o błędzie nie jest najlepszą metodą rozwiązania problemu.

Stwórzmy funkcję, która będzie logowała nasze komunikaty i jej wywołanie:

function zaloguj(...komunikaty: string[]) {
    console.log("Ależ skomplikowany program!", ...komunikaty);
}

zaloguj("Ja", "cię", "nie", "mogę");

Skompilujmy i uruchommy. Wprawdzie działa super, ale gdyby się coś popsuło, to musielibyśmy skorzystać z debuggera. To zajrzyjmy na zakładkę debuggera w narzędziach deweloperskich. Ops, tym razem plik main.js nie wygląda zbyt podobnie do naszego main.ts. Gdybyśmy chcieli widzieć nasz program musimy stworzyć source map, czyli plik opisujący związek oryginalnych źródeł z wygenerowanym .js. Pewnie można ręcznie, ale prościej poprosić kompilator:

tsc --sourceMap main.ts

Teraz w debuggerze mamy zarówno wygenerowany plik .js (przydatny, gdy tłumaczenie nie jest takie, jak oczekiwaliśmy i chcemy to wyjaśnić) jak i .ts, z którego zwykle korzystamy gdy chcemy się zorientować co się dzieje z naszym programem.

Krok 3: chcemy wczytać dane w formacie JSON

Wiecej o JSON

Zwykle dostaniemy je z sieci, ale tu stwórzmy w naszym programie zmienną:

let jsonString: string = `{
    "piloci": [
        "Pirx",
        "Exupery",
        "Idzikowski",
        "Główczewski"
    ],
    "lotniska": {
        "WAW": ["Warszawa", [3690, 2800]],
        "NRT": ["Narita", [4000, 2500]],
        "BQH": ["Biggin Hill", [1802, 792]],
        "LBG": ["Paris-Le Bourget", [2665, 3000, 1845]]
    }
}`;

Chcielibyśmy dostać jakąś strukturę danych, w której będą te wszystkie informacje. Niby nic prostszego:

let dataStructure = JSON.parse(jsonString);

console.log(dataStructure);

Gdy spojrzymy na wywnioskowany typ dataStructure, to będzie to any. Nie lubimy any, bo nie wiadomo co można z nim zrobić. Przygotujmy więc odpowiednie struktury danych. Chcemy mieć interfejs ILotnisko opisujący dane lotniska, typ Pilot opisujący pilota i interfejs ILiniaLotnicza opisujący całą zawartość jsonString.

[Tu piszemy te typy]

Możemy teraz dodać typ do deklaracji:

let dataStructure: ILiniaLotnicza = JSON.parse(jsonString);

i oczywiście zmienna będzie już tego typu. Możemy sprawdzić ilu jest pilotów:

console.log(jsonData.piloci.length);

Wygląda na to, że jest świetnie, na konsoli nic się wprawdzie nie zmieniło, bo po tłumaczeniu na JS nie ma już informacji o typach, ale w edytorze działa podpowiadanie, a więc pewnie i kontrola typów.

Prawie. Wykasujmy cały klucz piloci z jsonString i skompilujmy program.

TypeError! Jak to? Niestety, gdy przypisujemy wartość typu any do zmiennej jakiegoś konkretnego typu, kompilator wierzy, że wiemy co robimy. JSON.parse nie sprawdza, czy dane mają jakąś konkretną zawartość, a wyłącznie czy są poprawne składniowo. Musimy się napracować i sprawdzić sami. Napiszmy funkcję, która sprawdza, czy dane są poprawne i zwróci true lub false.

[Tu piszemy kod, który sprawdzi, czy dane zawierają wszystkie potrzebne informacje]

Teraz po wywołaniu tej funkcji możemy być pewni, że dane są poprawne. Chyba że pomyliliśmy się w treści funkcji - warto by ją przetestować, ale o testowaniu będzie później.

Gdy zadeklarujemy funkcję następująco:

function sprawdzDaneLiniiLotniczej(dane: any): dane is ILiniaLotnicza {

to nawet prosta deklaracja zmiennej:

let daneLiniiLotniczej = sprawdzDaneLiniiLotniczej(JSON.parse(jsonString));

wystarczy kompilatorowi do wywnioskowania, że dane są faktycznie typu ILiniaLotnicza.

Krok 4: DOMowe sprawy

Wróćmy do strony z ostatnich zajęć o HTML/CSS. Zrobiliśmy tam popup, który był mało użyteczny. Wyświetlał się na stałe i nic nie można było z nim zrobić. Spróbujmy doprowadzić do tego, żeby popup wyświetlał się tylko po tym, gdy spróbujemy wysłać formularz rejestracji z pustym imieniem albo nazwiskiem pasażera lub z datą lotu wcześniejszą niż aktualna. Tekst w popupie powinien opisywać napotkany błąd. No i chcemy móc zamknąć popup gdy już przeczytamy o błędach.

Spróbujmy dostać się z poziomu JS do jakiegoś elementu strony - np. do przycisku wysyłającego formularz. Znamy jego selektor (jeśli nie, to można go znaleźć z pomocą narzędzi deweloperskich), spróbujmy więc dostać się do obiektu JS związanego z wyświetlanym elementem.


DOM - co to jest?


W konsoli możemy wyszukać element wpisując coś w rodzaju:

let el = document.querySelector("input[type=submit]");

Teraz możemy obejrzeć dostępne z JS właściwości tego elementu. Niestety wyświetlona lista to tylko atrybuty, bez metod. Metody możemy zobaczyć wpisując w konsoli el., i patrząc na wyniki autouzupełniania. Jest ich raczej dużo, więc będziemy szukali i używali tylko tych potrzebnych do wykonania tego zadania.

Spróbujmy ukryć przycisk. Można usunąć go z drzewa:

el.remove();

ale gdy możemy chcieć wyświetlić go ponownie wygodniej jest zmienić właściwość display z CSS. Można to zrobić bezpośrednio:

el.style.display = "none";

albo modyfikować klasy do których należy element:

el.classList.add("hidden");

Oczywiście w ten sposób możemy dowolnie modyfikować wygląd elementu. Warto sprawdzić w Inspektorze jak wyglądają wprowadzane w taki sposób zmiany.

Po eksperymentach z użyciem konsoli dopiszmy do skryptu wczytywanego przez naszą stronę ukrywanie przycisku. Niestety, na konsoli pojawia się błąd, a przycisk ciągle widać. Wynika to z tego, że skrypt jest wczytywany i wykonuje się zanim przeglądarka stworzy całe drzewo DOM, bo jest wczytywany na początku strony, w <head>. Przenieśmy znacznik <script> na sam koniec strony, tuż przed </body>, i już wszystko będzie OK. Inną metodę radzenia sobie z tym problemem poznamy później.

Wyświetlmy teraz zawartość pola input z imieniem. Jeśli coś do niego wpiszemy, to w Inspektorze będzie widać, że jest atrybut value, który zawiera ten tekst. Spróbujmy dopisać do naszego skryptu logowanie zawartości tego pola. Niestety edytor zupełnie nie chce zauważyć, że znaleziony przez querySelector element ma atrybut value. Gdy wpiszemy nazwę ręcznie, to kod oczywiście zadziała, ale chcielibyśmy używać poprawnych typów. Potrzebujemy poznać typ elementu, żeby móc go użyć w edytorze. Z powodu skomplikowanej historii obiektów w JS typ możemy sprawdzić pisząc:

el.constructor.name;

jeśli el to nasz element. Gdy w edytorze napiszemy więc:

let el = document.querySelector("input[name=imie]") as HTMLInputElement;

to podpowiadanie składni zacznie działać, a TypeScript będzie wiedział, że jesteśmy pewni, że ten element ma taki typ. Jeśli nie jesteśmy pewni, warto skorzystać z operatora instanceof żeby się upewnić.

Teraz zmodyfikujmy jakiś tekst na stronie. Wybierzmy dowolny akapit (<p>), a jeśli takiego nie ma - to go dopiszmy. Jak pobrać jego zawartość i jak ją zmienić? Można skorzystać z wielu różnych atrybutów, m.in. textContent, innerText, innerHTML. Ich opisy można znaleźć na MDN.

Dodawanie elementów też nie jest trudne. Musimy utworzyć element, np. następująco:

let nowyElement = document.createElement("<div>");

a potem zmodyfikować jego zawartość; atrybuty możemy dodawać przez setAttribute, a dodatkowe elementy i teksty w środku stworzonego elementu przez przypisanie do właściwości innerHTML. Można też je tworzyć wywołując znowu createElement.

Utworzony element trzeba dodać do drzewa. Służą do tego metody appendChild i insertBefore.

Stwórzmy nowy akapit i dodajmy go na końcu strony, tuż przed </body>.

Krok 5: czas na nas

Czasem będziemy chcieli wykonać jakąś akcję po określonym czasie. Możemy skorzystać z funkcji setTimeout, żeby spowodować wywołanie naszego kodu mniej więcej po podanym w milisekundach czasie. Dopiszmy do naszej strony wywołanie, które po 2 sekundach od załadowania strony wypisze na konsoli tekst o tym, że minęły 2 sekundy od załadowania strony. Wyjdzie coś w rodzaju:

setTimeout(() => {
  console.log("No już wreszcie.");
}, 2000);

Uwaga:
- setTimeout to właściwie window.setTimeout
- zastanów się czemu wywołanie nastąpi po mniej więcej podanym czasie (stackoverflow?)
- nie zapomnij zrobiś callback!

Do wyłączenia wcześniej ustawionego zegarka służy clearTimeout, a jeśli chcemy wywołań cyklicznie co określony czas, to możemy użyć setInterval~/~clearInterval albo wołać setTimeout po zakończeniu każdego kolejnego wywołania.

Zadanie dodatkowe:

Napisać kod, który wyświetli popup z informacją o imieniu po kilku sekundach od wczytania strony, a po kolejnych kilku go zamknie.



Krok 6: obiecanki

Gdy chcemy wykonać kilka kolejnych kroków, każdy kilka sekund po poprzednim, to kod wygląda niezbyt fajnie:

function teczoweKolory(el: HTMLElement) {
    setTimeout(function () {
        console.log('red');
        el.style.backgroundColor = 'red';
        setTimeout(function() {
            el.style.backgroundColor = 'orange';
            setTimeout(function() {
                el.style.backgroundColor = 'yellow';
                setTimeout(function() {
                    el.style.backgroundColor = 'green';
                    setTimeout(function() {
                        el.style.backgroundColor = 'blue';
                        setTimeout(function() {
                            el.style.backgroundColor = 'indigo';
                            setTimeout(function() {
                                el.style.backgroundColor = 'purple';
                            }, 1000);
                        }, 1000);
                    }, 1000);
                }, 1000);
            }, 1000);
        }, 1000);
    }, 1000);
}

Ale po zastosowaniu do listy lotów działa.

Żeby wyglądało lepiej, potrzebujemy jakiegoś innego narzędzia niż proste callbacki. Promise tutaj zdecydowanie pomoże.

Napisz jednoargumentową funkcję zwracającą Promise i opóźniającą wykonanie programu o podaną w parametrze liczbę milisekund.

Na przykład coś w rodzaju:

function wait(ms: number) {
    return new Promise((resolve, reject) => {
        window.setTimeout(resolve, ms);
    });
}

Korzystając z tej funkcji zmień powyższą funkcję tęczoweKolory tak, żeby miała mniej wcięć.

Zadanie z gwiazdką: zmieniaj kolory w pętli, tak żeby w kodzie programu było tylko jedno wywołanie wait. Oczywiście skorzystaj z Promise.

function teczoweKolory(el: HTMLElement) {
    const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'purple'];
    let p = new Promise<void>((resolve, reject) => {
        resolve();
    });
    for(const color of colors) {
        p = p.then(() => wait(1000)).then(() => {
            console.log(color);
            el.style.backgroundColor = color;
        });
    }
}

Drugie zadanie z gwiazdką: Jeszcze raz to samo, ale Promise występuje jawnie tylko w funkcji oczekiwania.

async function teczoweKolory(el: HTMLElement) {
    const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'purple'];
    for(const color of colors) {
        await wait(1000);
        console.log(color);
        el.style.backgroundColor = color;
    }
}

Z Promise'ów można korzystać do zupełnie innych, asynchronicznych rzeczy, takich jak obsługa pobierania danych za pomocą fetch.

https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

Kilka słów o fetch

Zadanie: wyświetl zdjęcie autora najnowszego commitu z repozytorium TypeScript na githubie. Podpowiedź: url to https://api.github.com/repos/Microsoft/TypeScript/commits, odpowiedź będzie tekstem, więc można skorzystać z text(). W przeglądarce można zobaczyć zawartość JSONa. Oczywiście warto pamiętać o wykrywaniu błędów.

Zadanie z gwiazdką: wyświetl lub wypisz na konsoli listę repozytoriów autora najnowszego commitu posortowaną alfabetycznie bez odróżniania wielkich i małych liter.

fetch('https://api.github.com/repos/Microsoft/TypeScript/commits', {method: 'GET'}).then(response => {
    if(!response.ok) {
        throw new Error('fetch (1) failed');
    }
    return response.text();
}).then((text) => {
    let data = JSON.parse(text);
    let authorReposUri = data[0].author.repos_url;
    return fetch(authorReposUri);
}).then((response) => {
    if(!response.ok) {
        throw new Error('fetch (2) failed');
    }
    return response.text();
}).then((text) => {
    let repos = JSON.parse(text);
    let names = repos.map(el => el.name);
    names.sort((a,b) => a.localeCompare(b, undefined, {sensitivity: 'accent'}));
    console.log(names);
}).catch((error) => {
    console.log('Error occured', error);
})

Krok 7: dużo się dzieje

Potrafimy już wyświetlić interesujące nas rzeczy, a nawet pobrać jakieś informacje z formularzy, ale trudno cały czas powiedzieć, żeby nasze strony były interaktywne. Zajmijmy się teraz reakcjami na czynności użytkownika naszej aplikacji.

Zadanie 7.1: na stronie z informacjami o locie obsłuż kliknięcia w prawej kolumnie (na dużym ekranie), czyli w opóźnione loty i formularz, zmieniając kolor tła po każdym kliknięciu.

Pytania: Czy kliknięcie w pole input formularza zmienia kolor tła? Ilu handlerów potrzeba do zrobienia zadania?

  1. Tak
  2. zakładając, że strona jest zrobiona gridem, to jednego

Uwaga dodatkowa: tak jednego, ... ale na całej tabelce i trzeba filtrować kliknięcia w inne elementy, zaraz zobaczymy jak to zrobić, w przyszłości może będzie :nth-col(), ale na razie Firefox go nie obsługuje; bez tych cudów niestety aż dwóch, i to ewentualnego pustego miejsca w tabelce nie obsłużymy

Podłączmy procedurę obsługi zdarzenia z poprzedniego zadania do całego pojemnika zawierającego grid. W tym momencie obsługujemy oczywiście kliknięcia na całym obszarze grida, musimy więc odsiać kliknięcia w niewłaściwych miejscach.

Zadanie 7.2: korzystając z właściwości zdarzenia (event.target) obsłuż kliknięcia w opóźnione loty i formularz rezerwacji za pomocą tylko jednej procedury obsługi. Pewnie przyda się tu Node.contains.

Zadanie z dwiema gwiazdkami: obsłuż też kliknięcia w puste miejsce kolumny.

Konkluzja: To nie są super rozwiązania. Trzeba patrzyć w które miejsce (x,y) się kliknęło i próbować ustalić która to jest kolumna, np. sprawdzając lewą współrzędną któregoś elementu kolumny i jego szerokość.

Zadanie 7.3: zmień program tak, żeby zmianą koloru tła reagował na kliknięcie w dowolne miejsce tabelki za wyjątkiem obszaru formularza rezerwacji. W rozwiązaniu nie używaj event.target.

Tu pomysłem jest zrobienie handlera dla całej tabelki zmieniającego kolor tła i oddzielnego handlera dla formularza, który zrobi stopPropagation

W tym miejscu warto porozmawiać o tym, co wynika z powyższych zadań, czyli, że zdarzenia można obsługiwać w różnych miejscach drzewa, przerywać ich rozsyłanie itp.

Zadanie 7.4: dopisz do procedury obsługi zdarzenia wypisywanie 10*i-tej liczby Fibonacciego, gdzie i to kolejny numer kliknięcia. Wyliczaj ją metodą rekurencyjną.

Pytanie: co się dzieje z przeglądarką po kilku kliknięciach? Czemu?

Warto zachować tę funkcję, bo przyda się później.

Dyskusja o jednowątkowości.

Zadanie z gwiazdką: przerób obliczenia tak, żeby ciągle korzystały z tego samego wzoru, ale nie psuły przeglądarki.

Można zrobić z setTimeout(…,0), workerem albo pewnie na wiele innych sposobów. Przykra obserwacja jest taka, że async/await ani promisy się tu nie nadają.

document.querySelector('#przycisk').addEventListener('click', () => {
  console.log('prestart');
  let f1 = (i) => setTimeout(f2.bind(undefined, i, console.log), 0);
  let f2 = (i, completion) => {
    // console.log('f2', i);
    if(i < 2) {
      completion(i);
      return;
    }
    setTimeout(f2.bind(undefined, i-1, (fib1) => {
      setTimeout(f2.bind(undefined, i-2, (fib2) => {
        completion(fib1+fib2);
      }), 0);
    }), 0);
  };
  console.log('start');
  f1(20);
});

Oczywiście kliknięcie to nie jest jedyne zdarzenie, które możemy odebrać. Fajna lista jest np. pod adresem https://developer.mozilla.org/en-US/docs/Web/Events

Zobaczmy jeszcze dwa zdarzenia związane z formularzami: input i submit.

Zadanie 7.5: wyłącz przycisk submit w formularzu rezerwacji lotu i włącz go dopiero gdy wybrane będą lotniska, wybrana data nie będzie w przeszłości i wpisane będą imie i nazwisko (czyli co najmniej 2 słowa). Po wciśnięciu przycisku wyświetl potwierdzenie z informacjami z formularza.

operacje na datach: Date.parse(document.getElementById('datownik').value) - Date.now()

Innego rodzaju ważnymi zdarzeniami są load i DOMContentLoaded.

Zadanie 7.6: umieść w <head>...</head> skrypt, który wpisze do rezerwacji lotu jakieś imię i nazwisko.

Krok 8: ja to muszę sprawdzić

Mamy już wspaniałą stronę, teraz pora przekonać innych, że działa doskonale. W tym celu napiszemy testy, które bezsprzecznie to wykażą.

Zaczniemy od instalacji potrzebnych rzeczy. Do testowania kodu w TypeScripcie istnieje wiele bibliotek, my skorzystamy z Mocha i Chai. Dodatkowo od razu zainstalujemy rzeczy potrzebne do uruchamiania testów stron.

W katalogu z plikiem project.json wpiszmy następujące zaklęcie:

npm install mocha chai typescript ts-node selenium-webdriver mocha-webdriver @types/chai @types/mocha @types/selenium-webdriver --save-dev

Mocha to biblioteka do uruchamiania testów, Chai pozwoli nam pisać testy używając fajniejszej składni, ts-node nauczy Mochę uruchamiać testy napisane w TypeScripcie a selenium-webdriver będzie za nas uruchamiał przeglądarkę.

Testy piszemy oczywiście w plikach oddzielnych od kodu programu. Musimy więc umieć wczytać kod z pliku z kodem. Skupmy się na napisanej wcześniej funkcji wyliczającej liczby Fibonacciego. Tej najprostszej, blokującej przeglądarkę.

Musimy przerobić plik zawierający tę funkcję na moduł i wyeksportować z niego funkcję fib. Nic prostszego starczy zmienić napis function fib(... na export function fib(.... Wówczas w innym pliku możemy wczytać nasz moduł pisząc:

import fib from './program.ts'

zakładając, że funkcja jest w pliku program.ts. Obszerny opis modułów jest pod adresem http://exploringjs.com/es6/ch_modules.html

Kilka słów o modułach

Napiszmy teraz pierwszy test:

import { fib } from "./program";
import { expect } from "chai";
import "mocha";

describe("Fibonacci", () => {
    it("should equal 0 for call with 0", () => {
        expect(fib(0)).to.equal(42);
    });
});

i uruchommy go:

npx mocha -r ts-node/register testy.ts

Ojej, błąd. Musimy znaleźć, czy w teście, czy w programie.

Zadanie: napraw test i dopisz kilka kolejnych. Instrukcja chai jest tu: https://www.chaijs.com/api/bdd/

Zadanie z gwiazdką: przetestuj nieblokującą wersję funkcji.

ja zrobiłem coś takiego, ale jest masa innych możliwych rozwiązań:

import { expect } from "chai";
import "mocha";

async function fib2(i) {
    if(i < 2) return i;
    let fibm1 = await fib2(i-1),
        fibm2 = await fib2(i-2);
    return fibm1 + fibm2;
}

describe("Fibonacci", () => {
    it("should equal 0 for call with 0", () => {
        expect(fib(0)).to.equal(42);
    });

    it("should equal 0 asynchronously", function (done) {
        fib2(4).then((res) => {
            if(res == 3) {
                done();
            } else {
                done(`błąd ${res}`);
            }
        });
    });
});

Zwykle kompletny program wyświetla jednak jakieś informacje w okienku przeglądarki. Gdy korzystamy z testów tak, jak do tej pory, to w ogóle nie ma żadnej przeglądarki. Będziemy chcieli uruchamiać Firefoxa, sprawdzać zawartość stron i wysyłać zdarzenia. Pomoże nam w tym już zainstalowane Selenium, ale do jego działania potrzebny będzie jeszcze geckodriver dostępny na stronie:

https://github.com/mozilla/geckodriver/releases

Trzeba go ściągnąć, rozpakować w jakimś katalogu, który jest w ścieżce, a następnie napisać geckodriver -V żeby się upewnić, że wszystko działa. Żeby móc pisać testy z funkcjami asynchronicznymi trzeba ustawić zmienną środowiska:

export TS_NODE_COMPILER_OPTIONS='{"lib": ["ES2015"]}'    

I wtedy można napisać kolejny plik z testami (zmieniając kilka nazw w nawiasach kwadratowych):

import {Builder, Capabilities} from 'selenium-webdriver';
import { expect } from 'chai';
import { driver } from 'mocha-webdriver';

describe('testDrugi', function () {
    it('should say something', async function() {
        this.timeout(20000);
        await driver.get('file://[ścieżka.do.pliku.ze.stroną].html');

        expect(await driver.find('[selektor.opisu.miasta.docelowego]').getText()).to.include('[miasto.docelowe]');
        await driver.find('input[type=text]').sendKeys('Jan Woreczko');
        await driver.find('button').doClick();
    });
})

Created: 2019-04-10