Atraso / espera em um caso de teste de teste da interface do usuário do Xcode

182

Estou tentando escrever um caso de teste usando o novo teste de interface do usuário disponível no Xcode 7 beta 2. O aplicativo tem uma tela de login na qual faz uma chamada para o servidor para fazer login. Há um atraso associado a isso, pois é uma operação assíncrona.

Existe uma maneira de causar um mecanismo de atraso ou espera no XCTestCase antes de prosseguir com as etapas adicionais?

Não há documentação adequada disponível e eu examinei os arquivos de cabeçalho das classes. Não foi possível encontrar nada relacionado a isso.

Alguma idéia / sugestão?

Tejas HS
fonte
13
Eu acho que NSThread.sleepForTimeInterval(1)deve funcionar
Kametrixom
Ótimo! Parece que funciona. Mas não tenho certeza se é a maneira recomendada de fazer isso. Eu acho que a Apple deveria dar uma maneira melhor de fazer isso. Pode ter que arquivar um radar
Tejas HS
Na verdade, acho que está tudo bem, é realmente a maneira mais comum de pausar o thread atual por um certo tempo. Se você quiser mais controle você também pode entrar em GCD (A dispatch_after, dispatch_queuecoisas)
Kametrixom
@Kametrixom Não marque o loop de execução - a Apple introduziu o teste assíncrono nativo no Beta 4. Veja minha resposta para obter detalhes.
Joe Masilotti 26/08/15
2
Swift 4.0 -> Thread.sleep (forTimeInterval: 2)
uplearnedu.com 28/17

Respostas:

168

O teste de interface do usuário assíncrona foi introduzido no Xcode 7 Beta 4. Para aguardar um rótulo com o texto "Olá, mundo!" Para aparecer, você pode fazer o seguinte:

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
let exists = NSPredicate(format: "exists == 1")

expectationForPredicate(exists, evaluatedWithObject: label, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

Mais detalhes sobre o teste de interface do usuário podem ser encontrados no meu blog.

Joe Masilotti
fonte
19
Infelizmente, não há como aceitar que o tempo limite tenha passado e seguir em frente - waitForExpectationsWithTimeoutfalhará automaticamente no teste, o que é bastante lamentável.
Jedidja
@Jedidja Na verdade, isso não acontece para mim com o XCode 7.0.1.
Bastian
@Bastian Hmm interessante; Vou ter que verificar isso novamente.
Jedidja # 15/15
1
isso não funciona para mim. Aqui está meu exemplo: let xButton = app.toolbars.buttons ["X"] let existe = NSPredicate (formato: "existe == 1") expectationForPredicate (existe, avaliadoWithObject: xButton, manipulador: nil) waitForExpectationsWithTimeout (10, manipulador: nil)
emoleumassi
O app.launch()parece apenas reiniciar o aplicativo. Isso é necessário?
Chris Prince
225

Além disso, você pode apenas dormir:

sleep(10)

Como os UITests são executados em outro processo, isso funciona. Não sei como é aconselhável, mas funciona.

mxcl
fonte
2
Algum tempo precisamos atrasar e não queremos que isso aconteça! graças
Tai Le
13
A melhor resposta que eu já vi :) eu adicionaria + 100 votos Se eu pudesse :) #
226 Bartłomiej Semańczyk 17/15 /
8
Eu gosto de NSThread.sleepForTimeInterval (0.2), pois você pode especificar atrasos de segundos. (sleep () aceita um parâmetro inteiro; somente múltiplos de segundo são possíveis).
Graham Perks
5
@GrahamPerks, sim, embora exista também:usleep
mxcl 18/02
3
Não é uma sugestão ruim (você não entende como o UITesting funciona), mas mesmo que seja uma sugestão ruim, às vezes não há como criar uma expectativa que funcione (o sistema alerta alguém?), Então é isso que você tem.
Mxcl 31/05
78

iOS 11 / Xcode 9

<#yourElement#>.waitForExistence(timeout: 5)

Este é um ótimo substituto para todas as implementações personalizadas neste site!

Verifique minha resposta aqui: https://stackoverflow.com/a/48937714/971329 . Aqui, descrevo uma alternativa à espera de solicitações, o que reduzirá bastante o tempo de execução dos testes!

blackjacx
fonte
Graças @daidai eu mudei o texto :)
blackjacx
1
Sim, essa ainda é a abordagem que eu vou usar ao usar XCTestCasee funciona como um encanto. Eu não entendo por que abordagens como sleep(3)são votadas tão alto aqui, pois prolonga artificialmente o tempo de teste e não é realmente uma opção quando o seu conjunto de testes cresce.
blackjacx
Na verdade, requer Xcode 9, mas funciona em dispositivos / simuladores rodando iOS 10, bem ;-)
d4Rk
Sim, eu escrevi isso na manchete acima. Mas agora a maioria das pessoas deveria ter atualizado para pelo menos Xcode 9 ;-)
blackjacx
77

Xcode 9 introduziu novos truques com o XCTWaiter

O caso de teste aguarda explicitamente

wait(for: [documentExpectation], timeout: 10)

A instância do garçom delega para testar

XCTWaiter(delegate: self).wait(for: [documentExpectation], timeout: 10)

A classe Waiter retorna o resultado

let result = XCTWaiter.wait(for: [documentExpectation], timeout: 10)
switch(result) {
case .completed:
    //all expectations were fulfilled before timeout!
case .timedOut:
    //timed out before all of its expectations were fulfilled
case .incorrectOrder:
    //expectations were not fulfilled in the required order
case .invertedFulfillment:
    //an inverted expectation was fulfilled
case .interrupted:
    //waiter was interrupted before completed or timedOut
}

uso de amostra

Antes do Xcode 9

Objetivo C

- (void)waitForElementToAppear:(XCUIElement *)element withTimeout:(NSTimeInterval)timeout
{
    NSUInteger line = __LINE__;
    NSString *file = [NSString stringWithUTF8String:__FILE__];
    NSPredicate *existsPredicate = [NSPredicate predicateWithFormat:@"exists == true"];

    [self expectationForPredicate:existsPredicate evaluatedWithObject:element handler:nil];

    [self waitForExpectationsWithTimeout:timeout handler:^(NSError * _Nullable error) {
        if (error != nil) {
            NSString *message = [NSString stringWithFormat:@"Failed to find %@ after %f seconds",element,timeout];
            [self recordFailureWithDescription:message inFile:file atLine:line expected:YES];
        }
    }];
}

USO

XCUIElement *element = app.staticTexts["Name of your element"];
[self waitForElementToAppear:element withTimeout:5];

Rápido

func waitForElementToAppear(element: XCUIElement, timeout: NSTimeInterval = 5,  file: String = #file, line: UInt = #line) {
        let existsPredicate = NSPredicate(format: "exists == true")

        expectationForPredicate(existsPredicate,
                evaluatedWithObject: element, handler: nil)

        waitForExpectationsWithTimeout(timeout) { (error) -> Void in
            if (error != nil) {
                let message = "Failed to find \(element) after \(timeout) seconds."
                self.recordFailureWithDescription(message, inFile: file, atLine: line, expected: true)
            }
        }
    }

USO

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element)

ou

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element, timeout: 10)

FONTE

Ted
fonte
1
à procura de mais alguma ilustração sobre acima exemplo xcode9
rd_
1
Testado. Funciona como um encanto! Obrigado!
Dawid Koncewicz
32

A partir do Xcode 8.3, podemos usar XCTWaiter http://masilotti.com/xctest-waiting/

func waitForElementToAppear(_ element: XCUIElement) -> Bool {
    let predicate = NSPredicate(format: "exists == true")
    let expectation = expectation(for: predicate, evaluatedWith: element, 
                                  handler: nil)

    let result = XCTWaiter().wait(for: [expectation], timeout: 5)
    return result == .completed
}

Outro truque é escrever uma waitfunção, o crédito é dado a John Sundell por me mostrar

extension XCTestCase {

  func wait(for duration: TimeInterval) {
    let waitExpectation = expectation(description: "Waiting")

    let when = DispatchTime.now() + duration
    DispatchQueue.main.asyncAfter(deadline: when) {
      waitExpectation.fulfill()
    }

    // We use a buffer here to avoid flakiness with Timer on CI
    waitForExpectations(timeout: duration + 0.5)
  }
}

e use-o como

func testOpenLink() {
  let delegate = UIApplication.shared.delegate as! AppDelegate
  let route = RouteMock()
  UIApplication.shared.open(linkUrl, options: [:], completionHandler: nil)

  wait(for: 1)

  XCTAssertNotNil(route.location)
}
onmyway133
fonte
11

Com base na resposta de @ Ted , usei esta extensão:

extension XCTestCase {

    // Based on https://stackoverflow.com/a/33855219
    func waitFor<T>(object: T, timeout: TimeInterval = 5, file: String = #file, line: UInt = #line, expectationPredicate: @escaping (T) -> Bool) {
        let predicate = NSPredicate { obj, _ in
            expectationPredicate(obj as! T)
        }
        expectation(for: predicate, evaluatedWith: object, handler: nil)

        waitForExpectations(timeout: timeout) { error in
            if (error != nil) {
                let message = "Failed to fulful expectation block for \(object) after \(timeout) seconds."
                self.recordFailure(withDescription: message, inFile: file, atLine: line, expected: true)
            }
        }
    }

}

Você pode usá-lo assim

let element = app.staticTexts["Name of your element"]
waitFor(object: element) { $0.exists }

Também permite aguardar que um elemento desapareça ou qualquer outra propriedade seja alterada (usando o bloco apropriado)

waitFor(object: element) { !$0.exists } // Wait for it to disappear
Ben Lings
fonte
+1 muito swifty, e ele usa o predicado bloco que eu acho que é muito melhor porque as expressões predicado padrão não funcionou para mim às vezes, por exemplo, quando à espera de algumas propriedades em XCUIElements etc.
lawicko
10

Editar:

Na verdade, me ocorreu que no Xcode 7b4, o teste da interface do usuário agora expectationForPredicate:evaluatedWithObject:handler:

Original:

Outra maneira é girar o loop de execução por um período de tempo definido. Realmente útil apenas se você souber quanto tempo (estimado) precisará aguardar

Obj-C: [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow: <<time to wait in seconds>>]]

Rápido: NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: <<time to wait in seconds>>))

Isso não é super útil se você precisar testar algumas condições para continuar seu teste. Para executar verificações condicionais, use um whileloop.

enmiller
fonte
Isso é limpo e muito útil para mim, especialmente, por exemplo, aguardar o lançamento do aplicativo, solicitar dados pré-carregados e fazer coisas de login / logout. Obrigado.
Felixwcf #
4

O código a seguir funciona apenas com o objetivo C.

- (void)wait:(NSUInteger)interval {

    XCTestExpectation *expectation = [self expectationWithDescription:@"wait"];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [expectation fulfill];
    });
    [self waitForExpectationsWithTimeout:interval handler:nil];
}

Basta ligar para esta função, conforme indicado abaixo.

[self wait: 10];
arango_86
fonte
Erro -> capturado "NSInternalInconsistencyException", "Violação da API - chamada feita para aguardar sem que nenhuma expectativa tenha sido definida."
FlowUI. SimpleUITesting.com 04/04/19
@ iOSCalendarpatchthecode.com, você encontrou uma solução alternativa para isso?
Max
@ Max você pode usar algum dos outros nesta página?
FlowUI. SimpleUITesting.com 12/11/19
@ iOSCalendarpatchthecode.com Não, só preciso de algum atraso sem nenhum elemento para verificar. Então, eu preciso alternar isso.
Max
@ Max eu usei a resposta selecionada nesta página. Funcionou para mim. Talvez você possa perguntar a eles o que especificamente você está procurando.
FlowUI. SimpleUITesting.com 13/11/19
4

No meu caso, sleepcriou efeito colateral, então eu useiwait

let _ = XCTWaiter.wait(for: [XCTestExpectation(description: "Hello World!")], timeout: 2.0)
yoAlex5
fonte
0

De acordo com a API do XCUIElement, .existspode ser usado para verificar se uma consulta existe ou não, portanto a sintaxe a seguir pode ser útil em alguns casos!

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
while !label.exists {
    sleep(1)
}

Se você tem certeza de que sua expectativa será atendida, tente executar isso. Note-se que a falha pode ser preferível se a espera for muito longa; nesse caso, a waitForExpectationsWithTimeout(_,handler:_)postagem de @Joe Masilotti deve ser usada.

Reid
fonte
0

o sono bloqueará o fio

"Nenhum processamento de loop de execução ocorre enquanto o encadeamento está bloqueado."

você pode usar waitForExistence

let app = XCUIApplication()
app.launch()

if let label = app.staticTexts["Hello, world!"] {
label.waitForExistence(timeout: 5)
}
zdravko zdravkin
fonte
0

Isso criará um atraso sem interromper o encadeamento ou gerar um erro no tempo limite:

let delayExpectation = XCTestExpectation()
delayExpectation.isInverted = true
wait(for: [delayExpectation], timeout: 5)

Como a expectativa é invertida, o tempo limite é excedido.

Ken Murphy
fonte