Source: server/__tests__/e2e/versus/versus.test.js

/**
 * @jest-environment node
 */
import { createServer } from 'http';
import { server } from 'websocket';
import { handleRequest } from '../../../main.js';
import { getWinningCard } from '../../../util.js';
import puppeteer from 'puppeteer';
import { join } from 'path';

// eslint-disable-next-line no-undef
const VERSUS_PAGE_PATH = `${join(import.meta.url, '../../../../../client/versus.html')}`;

// eslint-disable-next-line no-undef
const HOME_PAGE_PATH = `${join(import.meta.url, '../../../../../client/index.html')}`;

const PORT = 3000;

const webSocketServer = new server({
  httpServer: createServer().listen(PORT),
  autoAcceptConnections: false,
});

let browser1;
let page1;
let browser2;
let page2;

/**
 * Automates initialization of browser and navigation to versus page, to be run before each test
 */
async function clientInitialization() {
  // Client init
  browser1 = await puppeteer.launch({
    args: ['--allow-file-access-from-files'],
    headless: 'false',
  });
  page1 = await browser1.newPage();
  await page1.goto(VERSUS_PAGE_PATH, { waitUntil: 'networkidle2' });

  browser2 = await puppeteer.launch({
    args: ['--allow-file-access-from-files'],
    headless: false,
  });
  page2 = await browser2.newPage();
  await page2.goto(VERSUS_PAGE_PATH, { waitUntil: 'networkidle2' });

  // Game start
  const selfGameCode = await page1.$('#self_game_code');
  const joinGameButton = await page2.$('#join_game_button');
  const startGameButton = await page1.$('#start_game_button');
  const gameCode = await selfGameCode.evaluate((i) => i.value);
  await page2.keyboard.type(gameCode);
  await joinGameButton.evaluate((b) => b.click());
  await startGameButton.evaluate((b) => b.click());
}

describe('E2E chat interaction testing', () => {
  beforeAll(() => {
    webSocketServer.on('request', handleRequest);
  });

  afterAll(() => {
    webSocketServer.closeAllConnections();
  });

  beforeEach(clientInitialization, 10000);

  it('Should show message from player 1 to player 2', async () => {
    const chatButton = await page1.$('#chat_input_button');
    const chatFeed = await page2.$('#chat_feed');
    const testMessage = 'test message';
    await page1.focus('#chat_input');
    await page1.keyboard.type(testMessage);
    await chatButton.evaluate((b) => b.click());
    await new Promise((r) => setTimeout(r, 500));
    const receivedMessage = await chatFeed.evaluate(
      (i) => i.lastChild.lastChild.innerText,
    );
    expect(receivedMessage).toBe(testMessage);
    await page1.close();
    await browser1.close();
    await page2.close();
    await browser2.close();
  }, 10000);

  it('Should send disconnect message to player 2 on player 1 disconnect', async () => {
    const chatFeed = await page2.$('#chat_feed');
    const disconnectMessage = 'User disconnected';
    await page1.close();
    await new Promise((r) => setTimeout(r, 500));
    const receivedMessage = await chatFeed.evaluate(
      (i) => i.lastChild.lastChild.innerText,
    );
    expect(receivedMessage).toBe(disconnectMessage);
    await browser1.close();
    await page2.close();
    await browser2.close();
  }, 10000);

  it('Should send reconnect message to player 2 on player 1 reconnect', async () => {
    const chatFeed = await page2.$('#chat_feed');
    const reconnectMessage = 'User reconnected';
    await page1.goto(HOME_PAGE_PATH, { waitUntil: 'networkidle2' });
    await page1.goto(VERSUS_PAGE_PATH, { waitUntil: 'networkidle2' });
    await new Promise((r) => setTimeout(r, 500));
    const receivedMessage = await chatFeed.evaluate(
      (i) => i.lastChild.lastChild.innerText,
    );
    expect(receivedMessage).toBe(reconnectMessage);
    await page1.close();
    await browser1.close();
    await page2.close();
    await browser2.close();
  }, 10000);

  it('Should send leave message to player 2 on player 1 leave', async () => {
    const chatFeed = await page2.$('#chat_feed');
    const leaveButton = await page1.$('#leave_game_button');
    const confirmLeaveButton = await page1.$('#confirm_leave_button');
    const leaveMessage = 'User left';
    await leaveButton.evaluate((b) => b.click());
    await confirmLeaveButton.evaluate((b) => b.click());
    await new Promise((r) => setTimeout(r, 500));
    const receivedMessage = await chatFeed.evaluate(
      (i) => i.lastChild.lastChild.innerText,
    );
    expect(receivedMessage).toBe(leaveMessage);
    await page1.close();
    await browser1.close();
    await page2.close();
    await browser2.close();
  }, 10000);

  it('Should show card face down to player 2 when player 1 plays card', async () => {
    const userCards = await page1.$('#user_cards');
    const oppPlayedCard = await page2.$('#opp_played_card');
    const expectedVariant = 'back';
    await new Promise((r) => setTimeout(r, 2000));
    await userCards.evaluate((e) => e.firstChild.click());
    await new Promise((r) => setTimeout(r, 500));
    const cardVariant = await oppPlayedCard.evaluate((e) =>
      e.firstChild.getAttribute('variant'),
    );
    expect(cardVariant).toBe(expectedVariant);
    await page1.close();
    await browser1.close();
    await page2.close();
    await browser2.close();
  }, 10000);

  it('Should reveal opponent card when both players play cards', async () => {
    const userCards1 = await page1.$('#user_cards');
    const userCards2 = await page2.$('#user_cards');
    const oppPlayedCard = await page2.$('#opp_played_card');
    const expectedVariant = 'front';
    await new Promise((r) => setTimeout(r, 2000));
    await userCards1.evaluate((e) => e.firstChild.click());
    await userCards2.evaluate((e) => e.firstChild.click());
    await new Promise((r) => setTimeout(r, 1000));
    const cardVariant = await oppPlayedCard.evaluate((e) =>
      e.firstChild.getAttribute('variant'),
    );
    expect(cardVariant).toBe(expectedVariant);
    await page1.close();
    await browser1.close();
    await page2.close();
    await browser2.close();
  }, 10000);

  it('Should show the appropriate round winner when both players play cards', async () => {
    const userCards1 = await page1.$('#user_cards');
    const userCards2 = await page2.$('#user_cards');
    const roundWinnerText = await page1.$('#round_end_text');
    await new Promise((r) => setTimeout(r, 2000));
    const card1 = await userCards1.evaluate((e) => {
      return {
        suite: e.firstChild.getAttribute('suite'),
        number: e.firstChild.getAttribute('number'),
      };
    });
    const card2 = await userCards2.evaluate((e) => {
      return {
        suite: e.firstChild.getAttribute('suite'),
        number: e.firstChild.getAttribute('number'),
      };
    });
    await userCards1.evaluate((e) => e.firstChild.click());
    await userCards2.evaluate((e) => e.firstChild.click());
    await new Promise((r) => setTimeout(r, 2500));
    if (getWinningCard(card1, card2, 'none') == card1) {
      const winText = await roundWinnerText.evaluate((e) => e.innerText);
      expect(winText).toBe('YOU WON!');
    } else {
      const loseText = await roundWinnerText.evaluate((e) => e.innerText);
      expect(loseText).toBe('YOU LOST!');
    }
    await page1.close();
    await browser1.close();
    await page2.close();
    await browser2.close();
  }, 10000);

  it('Should start next round when both players play cards', async () => {
    const userCards1 = await page1.$('#user_cards');
    const userCards2 = await page2.$('#user_cards');
    const roundNumberText = await page1.$('#round_number');
    await new Promise((r) => setTimeout(r, 2000));
    await userCards1.evaluate((e) => e.firstChild.click());
    await userCards2.evaluate((e) => e.firstChild.click());
    await new Promise((r) => setTimeout(r, 4000));
    const roundNumber = await roundNumberText.evaluate((e) => e.innerText);
    expect(roundNumber).toBe('2');
    await page1.close();
    await browser1.close();
    await page2.close();
    await browser2.close();
  }, 10000);

  it('Should show the appropriate game winner and end game when both players play all cards', async () => {
    const userCards1 = await page1.$('#user_cards');
    const userCards2 = await page2.$('#user_cards');
    const gameEndDialog = await page1.$('#instance_closed_modal');
    let wins1 = 0;
    let wins2 = 0;
    await new Promise((r) => setTimeout(r, 2000));
    for (let i = 0; i < 5; i++) {
      const currentWorldEventMeta = await page1.$('#current_world_event');
      const currentWorldEvent = await currentWorldEventMeta.evaluate((e) =>
        e.getAttribute('content'),
      );
      if (i == 1 || i == 3) {
        const worldEventButton1 = await page1.$('#world_event_button');
        const worldEventButton2 = await page2.$('#world_event_button');
        await worldEventButton1.evaluate((e) => e.click());
        await worldEventButton2.evaluate((e) => e.click());
      }
      const card1 = await userCards1.evaluate((e) => {
        return {
          suite: e.firstChild.getAttribute('suite'),
          number: e.firstChild.getAttribute('number'),
        };
      });
      const card2 = await userCards2.evaluate((e) => {
        return {
          suite: e.firstChild.getAttribute('suite'),
          number: e.firstChild.getAttribute('number'),
        };
      });
      await userCards1.evaluate((e) => e.firstChild.click());
      await userCards2.evaluate((e) => e.firstChild.click());
      if (getWinningCard(card1, card2, currentWorldEvent) == card1) {
        wins1 += 1;
      } else {
        wins2 += 1;
      }
      await new Promise((r) => setTimeout(r, 6000));
    }
    await new Promise((r) => setTimeout(r, 36000));
    const dialogOpen = await gameEndDialog.evaluate((e) => e.open);
    expect(dialogOpen).toBe(true);
    await page1.close();
    await browser1.close();
    await page2.close();
    await browser2.close();
  }, 120000);
});