Esquecer o await antes de uma assertion no Playwright produz "Argument of type 'Locator' is not assignable to parameter of type 'string'" em runtime. O TypeScript detecta o mesmo erro no editor antes de o teste rodar. O Playwright compila TypeScript internamente sem nenhum passo de build separado, e o autocomplete que isso desbloqueia significa que você não precisa memorizar a API.

O que TypeScript adiciona ao JavaScript

TypeScript é JavaScript com tipos. Tudo que você sabe em JavaScript funciona em TypeScript. Você está adicionando uma camada de anotações por cima.

// JavaScript
function getUser(id) {
  return fetchUser(id);
}

// TypeScript — mesma função com tipos
function getUser(id: number): Promise<User> {
  return fetchUser(id);
}

A anotação : number diz ao TypeScript que id deve ser um número. O : Promise diz o que a função retorna. Se você passar uma string onde um número é esperado, o TypeScript avisa imediatamente, antes do código rodar.

Por que TypeScript importa especificamente para o Playwright

Autocomplete. Quando você digita page. no VS Code, o TypeScript conhece cada método que existe em page e os exibe. Você não precisa memorizar a API. Detectar await faltando. Esqueceu o await antes de uma assertion? O TypeScript avisa:

Argument of type 'Locator' is not assignable to parameter of type 'string'

Segurança no Page Object. Quando você cria uma classe Page Object e chama seus métodos, o TypeScript garante que o método existe e que você está passando os tipos certos. Um typo no nome de um método é detectado imediatamente. Os tipos do Playwright são excelentes. O time do Playwright mantém as definições de tipo TypeScript como parte de primeira classe do projeto. Autocomplete e verificação de tipos funcionam out of the box sem configuração.

Configurando TypeScript para Playwright

Ao inicializar um projeto Playwright, escolher TypeScript é uma das opções:

npm init playwright@latest

Escolha TypeScript quando solicitado. Isso cria:

  • playwright.config.ts (arquivo de config em TypeScript)
  • tests/example.spec.ts (teste de exemplo em TypeScript)
  • tsconfig.json (configuração do TypeScript)

Se você já tem um projeto JavaScript, pode converter renomeando arquivos .js para .ts e adicionando um tsconfig.json.

Sintaxe básica de TypeScript para código de teste

Anotações de tipo

// Tipos primitivos
const email: string = 'admin@becomeqa.com';
const timeout: number = 30000;
const isLoggedIn: boolean = false;

// Na prática, o TypeScript infere esses tipos: raramente você precisa escrevê-los
const email = 'admin@becomeqa.com'; // TypeScript sabe que isso é uma string

O TypeScript infere tipos a partir dos valores. Você só precisa escrever anotações de tipo quando ele não consegue descobrir por conta própria.

Interfaces: definindo a forma dos objetos

// Define como um objeto usuário parece
interface User {
  email: string;
  password: string;
  role: 'admin' | 'viewer';
}

// Agora o TypeScript sabe o que esperar
const testUser: User = {
  email: 'admin@becomeqa.com',
  password: 'testpass123',
  role: 'admin',
};

// TypeScript vai dar erro se você adicionar um campo que não existe
const badUser: User = {
  email: 'admin@becomeqa.com',
  password: 'testpass123',
  role: 'admin',
  name: 'Alice', // Erro: 'name' does not exist in type 'User'
};

Interfaces em testes são mais úteis para dados de teste e parâmetros de construtores de Page Object.

Union types

// 'admin' | 'viewer' significa que o valor só pode ser uma dessas duas strings
type UserRole = 'admin' | 'viewer';

// Útil para campos de status, tipos que têm um conjunto fixo de valores
type ItemStatus = 'Planned' | 'In Progress' | 'Completed';

function selectStatus(status: ItemStatus) {
  // TypeScript sabe que status só pode ser um de três valores
}

selectStatus('Planned');    // ✓ válido
selectStatus('Cancelled'); // ✗ Erro: not assignable to type 'ItemStatus'

Union types previnem que valores inválidos entrem nos dados de teste.

Propriedades opcionais

interface TestItem {
  destination: string;
  status: ItemStatus;
  notes?: string; // O ? significa que essa propriedade é opcional
}

// Os dois são válidos
const item1: TestItem = { destination: 'Tokyo', status: 'Planned' };
const item2: TestItem = { destination: 'Paris', status: 'Completed', notes: 'Visitado' };

TypeScript no Page Object Model

O valor real do TypeScript para engenheiros de QA aparece nos Page Objects:

import { Page } from '@playwright/test';

// Importando o tipo Page do Playwright
export class LoginPage {
  // TypeScript sabe que page é um objeto Playwright Page
  constructor(private page: Page) {}

  async login(email: string, password: string): Promise<void> {
    await this.page.getByRole('button', { name: 'Login' }).click();
    await this.page.getByLabel('Username').fill(email);
    await this.page.getByLabel('Password').fill(password);
    await this.page.getByRole('button', { name: 'Submit' }).click();
  }

  async getErrorMessage(): Promise<string | null> {
    const error = this.page.getByRole('alert');
    if (await error.isVisible()) {
      return error.textContent();
    }
    return null;
  }
}

Quando você usa essa classe em um teste:

test('login com credenciais inválidas', async ({ page }) => {
  const loginPage = new LoginPage(page);
  
  // TypeScript sabe que login() recebe duas strings
  await loginPage.login('wrong@example.com', 'wrongpass');
  
  // TypeScript sabe que getErrorMessage() retorna Promise<string | null>
  const error = await loginPage.getErrorMessage();
  expect(error).toBeTruthy();
});

Se você chamar loginPage.loginn() (typo), o TypeScript detecta imediatamente. Se você chamar loginPage.login(123, 'password') (tipo errado), o TypeScript dá erro.

Tipos integrados do Playwright que você vai usar

import { Page, BrowserContext, Locator, APIRequestContext } from '@playwright/test';

// Page — o objeto principal da página do browser
// BrowserContext — uma sessão isolada de browser
// Locator — uma referência a elementos da página
// APIRequestContext — para testes de API

Você os importa ao escrever Page Objects tipados ou arquivos de fixture tipados.

Fixtures de teste tipadas

Quando você estende as fixtures do Playwright (para compartilhar page objects entre testes), os tipos TypeScript tornam isso confiável:

import { test as base, Page } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';

// Define o tipo das suas fixtures customizadas
type Fixtures = {
  loginPage: LoginPage;
};

// Estende o teste base com fixtures tipadas
export const test = base.extend<Fixtures>({
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await use(loginPage);
  },
});

// Nos testes, TypeScript sabe que loginPage é uma instância de LoginPage
test('teste com fixture', async ({ loginPage }) => {
  await loginPage.login('admin@becomeqa.com', 'testpass123');
});

tsconfig.json: o que importa

Quando npm init playwright@latest cria um tsconfig.json, ele funciona out of the box. As configurações que vale conhecer:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,       // ← Este é o importante
    "esModuleInterop": true,
    "outDir": "./dist"
  }
}

"strict": true ativa todas as verificações de rigor do TypeScript, incluindo:
  • Sem tipo any implícito
  • Verificações de null (TypeScript avisa quando você pode estar chamando métodos em null)
  • A verificação que captura Promises não aguardadas

Mantenha strict: true. Os avisos que ele gera estão quase sempre apontando para problemas reais.

O que você não precisa aprender (ainda)

TypeScript tem recursos úteis para desenvolvimento de aplicações em grande escala, mas raramente necessários em código de teste:

  • Generics: a menos que você esteja construindo utilitários de fixture reutilizáveis
  • Decorators: usados em frameworks mais antigos, não no Playwright moderno
  • Utility types (Partial, Readonly): ocasionalmente úteis, não necessários
  • Declaration files (.d.ts): para autores de bibliotecas, não para quem escreve testes

Aprenda-os quando encontrar um problema específico que eles resolvem.

Erros comuns de TypeScript em testes Playwright

Property 'X' does not exist on type 'Y'

Você está chamando um método ou acessando uma propriedade que não existe. Typo, ou você precisa importar o tipo certo.

Object is possibly 'null'

O TypeScript sabe que um método pode retornar null. Verifique o valor de retorno:

const text = await page.getByRole('heading').textContent();
// TypeScript: text pode ser null

if (text !== null) {
  expect(text).toBe('My Travel Items');
}
// Ou use a asserção non-null se tiver certeza:
expect(text!).toBe('My Travel Items');

Argument of type 'string' is not assignable to parameter of type 'number'

Tipo errado passado para uma função. Verifique a assinatura da função e corrija o argumento.

'await' has no effect on the type of this expression

Você está usando await em algo que não é uma Promise. Remova o await ou verifique por que o método não está retornando uma Promise.

JavaScript vs TypeScript: a decisão prática

Se você está começando do zero: TypeScript. A curva de aprendizado em relação ao JavaScript puro é mínima, e os benefícios (autocomplete, captura de erros) se pagam imediatamente.

Se você tem um projeto Playwright em JavaScript existente: considere migrar. Renomeie .js para .ts, adicione um tsconfig.json, corrija os erros de tipo. Para um projeto típico isso leva algumas horas.

Se o seu time usa JavaScript: arquivos TypeScript compilam para JavaScript, então coexistem. Você pode migrar arquivo por arquivo.

FAQ

Preciso compilar TypeScript antes de rodar os testes?

Não. O Playwright cuida da compilação internamente. Você roda npx playwright test exatamente como antes. O Playwright compila TypeScript on the fly usando seu transpilador integrado.

O TypeScript me dá erros, mas os testes ainda rodam. Por quê?

Erros de TypeScript são avisos do verificador de tipos, não erros de runtime. O test runner compila o código independentemente dos erros de tipo. O objetivo de corrigir erros TypeScript é capturar bugs antes que se tornem falhas de runtime, não fazer os testes rodarem.

Preciso entender todos os tipos TypeScript que o Playwright usa?

Não. Você precisa importar Page, Locator e APIRequestContext ao escrever page objects tipados. O resto você vai encontrar e consultar conforme necessário.

TypeScript é significativamente mais difícil de aprender do que JavaScript para testes?

As adições significativas para código de teste são: anotações de tipo em parâmetros de funções, interfaces para dados de teste, e importar tipos do Playwright. Se você já conhece JavaScript, aprender isso leva alguns dias, não semanas.

→ Veja também: Git e GitHub para QA Engineers: Um Tutorial Direto ao Ponto | Page Object Model no Playwright: Do Caos à Manutenibilidade