I have a c++ method that pops up a WPF dialog. The c++ method wraps a call of our .net library and gets the dialog output (c++ cli). The dialog is essentially a login dialog, asking for username and password. I want to test in c++ that I get the right dialog output when the dialog receives some username and password input.
In essence, my test goes as follows:
struct ShowCredentialsDialogContext
{
ShowCredentialsDialogContext(const std::function<Credentials()>& fnc)
: _fnc(fnc)
{ }
void Handle()
{
Creds = _fnc();
}
Credentials Creds;
private:
std::function<Credentials()> _fnc;
};
TEST_F(DialogsFixture, UsernamePasswordDialogReturnsEnteredCredentials)
{
// Arrange
ShowCredentialsDialogContext ctx(&MyLib::ShowUsernamePasswordDialog);
const auto& functionOnThread = std::bind(&ShowCredentialsDialogContext::Handle, &ctx);
std::thread thread(functionOnThread);
InitializeHandleToWindowWithTitle(L"Authentication required");
const std::wstring& expectedUsername = L"ųƢȝɬᴥ";
const std::wstring& expectedPassword = L"ỗỷⓩ✟ᵺ";
// Act
SendUnicodeInput(expectedUsername);
GoToNextControl();
SendUnicodeInput(expectedPassword);
ValidateDialog();
thread.join();
// Assert
ASSERT_EQ(expectedUsername, ctx.Creds.Username);
ASSERT_EQ(expectedPassword, ctx.Creds.Password);
}
where
class DialogsFixture : public BaseFixture
{
public:
DialogsFixture()
: _handle(nullptr)
{}
protected:
void InitializeHandleToWindowWithTitle(const std::wstring& title, int nbRetries = 10, int timeStepInMs = 100)
{
while (nbRetries >= 0)
{
if (const auto handle = FindWindowW(0, title.c_str()))
{
_handle = handle;
return;
}
--nbRetries;
std::this_thread::sleep_for(std::chrono::milliseconds(timeStepInMs));
}
throw TimeOutException("Unable to get handle to window");
}
void SendUnicodeInput(const std::wstring& msg) const
{
for (const auto ch : msg)
{
if (!PostMessageW(_handle, WM_CHAR, ch, NULL))
{
FAIL() << "got error " << GetLastError() << std::endl;
}
}
}
void GoToNextControl() const
{
SendControlInput(VK_TAB);
}
void ValidateDialog() const
{
SendControlInput(VK_RETURN);
}
void CancelDialog() const
{
SendControlInput(VK_ESCAPE);
}
private:
void SendControlInput(WORD ctrl) const
{
if (!PostMessageW(_handle, WM_KEYDOWN, ctrl, NULL))
{
FAIL() << "got error " << GetLastError() << std::endl;
}
}
private:
HWND _handle;
};
I chose to proceed with PostMessage instead of SendInput because the latter doesn't fill my dialog on my Team City agent while the former fills it as expected (I have other tests with simpler dialogs that are perfectly green).
My problem is now that the password is not set in the dialog after
SendUnicodeInput(expectedUsername);
GoToNextControl();
SendUnicodeInput(expectedPassword);
The username gets perfectly displayed, then the caret gets placed to the password input field, but then nothing happens. The password does not get displayed in the password field. I have another dialog containing only a password field and my test of that dialog works flawlessly. I can fill the password input field without problem. As soon as I send the VK_TAB any character I post afterwards does not get displayed. The same holds if I replace VK_TAB with VK_SPACE for example.
I am a bit clueless here, I have tried a lot of stuff, nothing seems to work. What am I doing wrong here?
Replacing the DialogsFixture with this makes it perfectly work on my local development machine:
class DialogsFixture : public BaseFixture
{
public:
DialogsFixture()
: _handle(nullptr)
{}
protected:
void InitializeHandleToWindowWithTitle(const std::wstring& title, int nbRetries = 10, int timeStepInMs = 100)
{
// same as before
[...]
}
void SendUnicodeInput(const std::wstring& msg) const
{
SetForegroundWindow(_handle);
std::vector<INPUT> vec;
for (const auto ch : msg)
{
INPUT input = { 0 };
input.type = INPUT_KEYBOARD;
input.ki.dwFlags = KEYEVENTF_UNICODE;
input.ki.wScan = ch;
vec.push_back(input);
input.ki.dwFlags |= KEYEVENTF_KEYUP;
vec.push_back(input);
}
SendInput(vec.size(), vec.data(), sizeof INPUT);
}
// same as before
[...]
private:
void SendControlInput(WORD ctrl) const
{
SetForegroundWindow(_handle);
INPUT ip;
ip.type = INPUT_KEYBOARD;
ip.ki.time = 0;
ip.ki.dwFlags = 0;
ip.ki.wScan = 0;
ip.ki.wVk = ctrl;
ip.ki.dwExtraInfo = 0;
SendInput(1, &ip, sizeof INPUT);
ip.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &ip, sizeof INPUT);
}
private:
HWND _handle;
};
Unfortunately, that doesn't work on my team city agent. It seems like the dialog receives no keyboard input, nothing gets displayed on it. With the PostMessage variant, the dialog receives data. How can I make my test work?