Spread e rest compartilham a mesma sintaxe ..., mas fazem coisas opostas: spread expande um array ou objeto no lugar, rest coleta múltiplos valores em um só. O ganho prático em automação de testes são as factories de dados: um objeto base espalhado com overrides cria todas as variantes sem repetir todos os campos.
Operador spread: expandindo coisas
O operador spread (...) expande um iterável (array ou objeto) no lugar.
Espalhando arrays
const parte1 = [1, 2, 3];
const parte2 = [4, 5, 6];
const combinado = [...parte1, ...parte2];
// [1, 2, 3, 4, 5, 6]
// Adicionar itens antes ou depois
const comExtras = [0, ...parte1, ...parte2, 7];
// [0, 1, 2, 3, 4, 5, 6, 7]Copiando um array
const original = ['alice', 'bob', 'charlie'];
const copia = [...original];
copia.push('dave'); // Modifica só a cópia
console.log(original); // ['alice', 'bob', 'charlie'] — inalteradoEspalhando objetos
const configBase = { timeout: 30000, headless: true };
const configCi = { ...configBase, timeout: 60000 };
// { timeout: 60000, headless: true }
// Nota: timeout foi sobrescrito pelo valor posteriorA chave posterior vence em caso de conflito.
Spread em dados de teste: o grande caso de uso
O padrão mais valioso para engenheiros de QA: criar variantes de objeto a partir de uma base:
const usuarioPadrao = {
email: 'teste@exemplo.com',
password: 'SenhaValida1',
role: 'member',
ativo: true,
emailVerificado: true,
};
// Criar variantes sem repetir todos os campos
const usuarioAdmin = { ...usuarioPadrao, role: 'admin' };
const usuarioInativo = { ...usuarioPadrao, ativo: false };
const naoVerificado = { ...usuarioPadrao, emailVerificado: false };
const emailCustomizado = { ...usuarioPadrao, email: 'custom@teste.com' };Essa é a base das factories de dados de teste. Defina o estado do happy path uma vez e crie todos os edge cases a partir dele.
Mesclando configs de teste
const configPlaywrightBase = {
use: {
baseURL: 'https://lab.becomeqa.com',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
timeout: 30_000,
};
// Overrides do ambiente de CI
const overridesCi = {
timeout: 60_000,
retries: 2,
};
const configFinal = { ...configPlaywrightBase, ...overridesCi };Construindo listas de casos de teste
const casoHappyPath = {
email: 'valido@exemplo.com',
password: 'SenhaValida1',
resultadoEsperado: 'sucesso',
statusEsperado: 200,
};
const casosDeTeste = [
casoHappyPath,
{ ...casoHappyPath, email: 'outro@teste.com' },
{ ...casoHappyPath, password: 'OutraSenhaValida2' },
{ ...casoHappyPath, email: 'invalido', resultadoEsperado: 'erro', statusEsperado: 422 },
];Muito mais limpo do que repetir todos os campos para cada caso de teste.
Parâmetros rest: coletando coisas
Parâmetros rest coletam múltiplos itens em um único parâmetro. Usa a mesma sintaxe ..., mas na definição da função, não na chamada:
// REST: coleta múltiplos argumentos em um array
function registrarResultados(nomeTeste: string, ...resultados: string[]) {
console.log(`Teste: ${nomeTeste}`);
resultados.forEach(r => console.log(` - ${r}`));
}
registrarResultados('Teste de login', 'e-mail validado', 'redirect funcionou', 'sessão criada');
// Teste: Teste de login
// - e-mail validado
// - redirect funcionou
// - sessão criadaO ...resultados coleta todos os argumentos após nomeTeste em um array.
Rest em destructuring
Rest também pode ser usado em destructuring para coletar "todo o resto":
const [primeiro, segundo, ...resto] = [1, 2, 3, 4, 5];
// primeiro = 1, segundo = 2, resto = [3, 4, 5]
const { email, password, ...outrosCampos } = usuario;
// email e password são extraídos
// outrosCampos = { role: 'member', ativo: true, ... }Útil quando você precisa de campos específicos e quer passar o resto adiante:
async function criarUsuarioEObterToken({ email, password, ...dadosPerfil }: DadosCriacaoUsuario) {
// Usa email e password para autenticação
const token = await auth.login(email, password);
// Usa o resto para configurar o perfil (sem os campos de auth)
await api.atualizarPerfil(token, dadosPerfil);
return token;
}Padrões práticos no Playwright
Padrão 1: Helper de teste flexível
type OpcaoClique = {
timeout?: number;
force?: boolean;
};
async function clicarEAguardar(
page: Page,
selector: string,
{ timeout = 5000, force = false, ...opcoes }: OpcaoClique = {}
) {
await page.locator(selector).click({ timeout, force, ...opcoes });
await page.waitForLoadState('networkidle');
}Padrão 2: Construindo headers de requisição
const headersPadrao = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
const headersAuth = {
...headersPadrao,
'Authorization': `Bearer ${token}`,
};
const headersAdmin = {
...headersAuth,
'X-Admin-Key': process.env.ADMIN_KEY,
};Padrão 3: Combinando dados de teste de múltiplas fontes
const dadosBase = await lerArquivoFixture('usuario-base.json');
const especificoAmbiente = await lerArquivoFixture(`${process.env.ENV}-overrides.json`);
const dadosTeste = { ...dadosBase, ...especificoAmbiente };
// Valores específicos do ambiente sobrescrevem os valores basePadrão 4: Coletando casos com falha
async function rodarTodosCasos(casos: CasoTeste[]): Promise<string[]> {
const falhas: string[] = [];
for (const caso of casos) {
try {
await rodarCaso(caso);
} catch (error) {
falhas.push(`${caso.nome}: ${error.message}`);
}
}
return falhas;
}
// No ponto de chamada:
const [primeiraFalha, ...outrasFalhas] = await rodarTodosCasos(casosDeTeste);
if (primeiraFalha) {
console.log('Primeira falha:', primeiraFalha);
console.log('Falhas adicionais:', outrasFalhas);
}Spread vs. rest: como distinguir
Spread — você está colocando coisas dentro (expandindo em um array/objeto literal):const arr = [...itens]; // expandindo itens para o array
const obj = { ...config }; // expandindo config para o objeto
func(...args); // expandindo args como argumentos de funçãofunction fn(...args) {} // coletando argumentos da chamada
const [a, ...resto] = array; // coletando o restante do array
const { x, ...outros } = obj; // coletando o restante do objetoMesmo símbolo .... O contexto determina o significado.
Um erro comum: cópia rasa
O spread cria uma cópia rasa: objetos aninhados ainda são referências compartilhadas.
const usuario = { nome: 'Alice', endereco: { cidade: 'São Paulo' } };
const copia = { ...usuario };
copia.nome = 'Bob'; // Muda só a cópia
copia.endereco.cidade = 'Rio de Janeiro'; // Muda TANTO usuario quanto copia
console.log(usuario.endereco.cidade); // 'Rio de Janeiro' — opsSe precisar de uma cópia profunda (objetos aninhados também independentes), use uma abordagem diferente:
// Cópia profunda simples (funciona para dados serializáveis em JSON)
const copiaFunda = JSON.parse(JSON.stringify(usuario));
// Ou use structuredClone (JS moderno)
const copiaFunda2 = structuredClone(usuario);Para a maioria dos objetos de dados de teste que são pares simples chave-valor sem objetos mutáveis aninhados, spread está ótimo. Mas conheça a limitação.
Resumo
| Sintaxe | Contexto | O que faz |
|---------|---------|-----------|
| [...arr] | Array literal | Expande arr no novo array |
| {...obj} | Object literal | Copia todas as propriedades de obj |
| fn(...args) | Chamada de função | Passa elementos do array como argumentos separados |
| function fn(...params) | Definição de função | Coleta múltiplos argumentos em array |
| const [a, ...resto] = arr | Destructuring de array | Coleta itens restantes |
| const {x, ...resto} = obj | Destructuring de objeto | Coleta propriedades restantes |
Quando o padrão fizer sentido, você vai reconhecê-lo em todo lugar. Aparece no use() do Playwright, em fixtures de teste, em arquivos de config e em toda factory de dados de teste que você vai ler.