diff --git a/README-automember.md b/README-automember.md
index 7fbc62ce75a36bfe22e3742dec83a111e942b668..0d2861e4354675c8da3941beca3db03099d758ff 100644
--- a/README-automember.md
+++ b/README-automember.md
@@ -122,6 +122,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | Automember rule. | yes
 `description` | A description of this auto member rule. | no
 `automember_type` | Grouping to which the rule applies. It can be one of `group`, `hostgroup`. | yes
diff --git a/README-automountlocation.md b/README-automountlocation.md
index f28afc2ac73e5543fe90254c5848a9ebb4869887..42393bc163ab635348ab86833e04256a0e859ea9 100644
--- a/README-automountlocation.md
+++ b/README-automountlocation.md
@@ -88,7 +88,7 @@ Example playbook to ensure absence of an automount location:
   - name: ensure automount locations LOCATION1 and LOCATION2 do not exist
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
-      name: 
+      name:
         - LOCATION1
         - LOCATION2
       state: absent
@@ -104,6 +104,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` \| `location` | List of one or more automountlocation names. | yes
 `state` | The state to ensure. It can be one of `present`, or `absent`, default: `present`. | no
 
diff --git a/README-config.md b/README-config.md
index c6b570fe23faaeffb1737cc2a9d46949f9606892..f7a5b68142d499b4cbd37bd66adc9e0f2c9395a3 100644
--- a/README-config.md
+++ b/README-config.md
@@ -91,6 +91,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `maxusername` \| `ipamaxusernamelength` |  Set the maximum username length (1 to 255) | no
 `maxhostname` \| `ipamaxhostnamelength` |  Set the maximum hostname length between 64-255. Only usable with IPA versions 4.8.0 and up. | no
 `homedirectory` \| `ipahomesrootdir` |  Set the default location of home directories | no
diff --git a/README-delegation.md b/README-delegation.md
index 63e4150b1fe3b41f27f3a3f2ba591f3cdf374c9b..00d84388b0f644b9aef7e70f08ecec72b37f3a15 100644
--- a/README-delegation.md
+++ b/README-delegation.md
@@ -142,6 +142,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `aciname` | The list of delegation name strings. | yes
 `permission` \| `permissions` |  The permission to grant `read`, `read,write`, `write`]. Default is `write`. | no
 `attribute` \| `attrs` | The attribute list to which the delegation applies. | no
diff --git a/README-dnsconfig.md b/README-dnsconfig.md
index 029ec515a16514984d9d8b742f7a0b1b7eee0028..f7e733fb29a9c9326b91298866f328fddb0828ac 100644
--- a/README-dnsconfig.md
+++ b/README-dnsconfig.md
@@ -126,6 +126,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `forwarders` | The list of forwarders dicts. Each `forwarders` dict entry has:| no
   | `ip_address` - The IPv4 or IPv6 address of the DNS server. | yes
   | `port` - The custom port that should be used on this server. | no
diff --git a/README-dnsforwardzone.md b/README-dnsforwardzone.md
index 249860817a7da3ff43ea88cddb291ff8771ee911..cd5b5cc50008445a6adc1181fa8eadaf4ab18bf2 100644
--- a/README-dnsforwardzone.md
+++ b/README-dnsforwardzone.md
@@ -107,6 +107,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | Zone name (FQDN). | yes if `state` == `present`
 `forwarders` \| `idnsforwarders` |  Per-zone forwarders. A custom port can be specified for each forwarder. Options | no
   | `ip_address`: The forwarder IP address. | yes
diff --git a/README-dnsrecord.md b/README-dnsrecord.md
index 6f88f4322cebfbc893b75dde71b31c8c959e1c71..35c3614c2d6470c40255dfddd0f30bb758023b18 100644
--- a/README-dnsrecord.md
+++ b/README-dnsrecord.md
@@ -249,6 +249,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `zone_name` \| `dnszone` | The DNS zone name to which DNS record needs to be managed. You can use one global zone name for multiple records. | no
   required: true
 `records` | The list of dns records dicts. Each `records` dict entry can contain **record variables**. | no
diff --git a/README-dnszone.md b/README-dnszone.md
index 308c58feba1f91771e41d3893c3e64e1a9576ae0..544b86ef208d74719b987297d0725bbd7f32d9a4 100644
--- a/README-dnszone.md
+++ b/README-dnszone.md
@@ -202,6 +202,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `zone_name` | The zone name string or list of strings. | no
 `name_from_ip` | Derive zone name from reverse of IP (PTR). Can only be used with `state: present`. | no
 `forwarders` | The list of forwarders dicts. Each `forwarders` dict entry has:| no
diff --git a/README-group.md b/README-group.md
index f845ef0582e33add6953b10f41bde443fcac0d6e..662fcd01bb3a2a09e238c61c8bc4e779b3ce20b0 100644
--- a/README-group.md
+++ b/README-group.md
@@ -154,6 +154,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of group name strings. | no
 `description` | The group description string. | no
 `gid` \| `gidnumber` | The GID integer. | no
diff --git a/README-hbacrule.md b/README-hbacrule.md
index a1b69877da2bb66e6b6ca54404258c8023ba060b..fee62820e9c8aef4915b39eabcd5b5f3f56ba94b 100644
--- a/README-hbacrule.md
+++ b/README-hbacrule.md
@@ -136,6 +136,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of hbacrule name strings. | yes
 `description` | The hbacrule description string. | no
 `usercategory` \| `usercat` | User category the rule applies to. Choices: ["all", ""] | no
diff --git a/README-hbacsvc.md b/README-hbacsvc.md
index 7203e54971c47ddda82fa0b0258712fc1e2ded5e..49644b8fb7eecdd416d83dc94e1d09cceb3fb2ad 100644
--- a/README-hbacsvc.md
+++ b/README-hbacsvc.md
@@ -98,6 +98,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` \| `service` | The list of hbacsvc name strings. | no
 `description` | The hbacsvc description string. | no
 `state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | no
diff --git a/README-hbacsvcgroup.md b/README-hbacsvcgroup.md
index 56d5f7a6880a6b55b7ea1659b1ea9a0f495a6560..c2beae418585ea5e08051c84352c82e85c1297a9 100644
--- a/README-hbacsvcgroup.md
+++ b/README-hbacsvcgroup.md
@@ -136,6 +136,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of hbacsvcgroup name strings. | no
 `description` | The hbacsvcgroup description string. | no
 `nomembers` | Suppress processing of membership attributes. (bool) | no
diff --git a/README-host.md b/README-host.md
index 56c92bdaa92899a40e16db6adf243156c3ac10f9..ba274c98c3e433c9d3a40bb69d17bdaad92d94c1 100644
--- a/README-host.md
+++ b/README-host.md
@@ -320,6 +320,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `fqdn` | The list of host name strings. `name` with *host variables* or `hosts` containing *host variables* need to be used. | no
 **Host variables** | Only used with `name` variable in the first level. | no
 `hosts` | The list of host dicts. Each `hosts` dict entry can contain **host variables**.<br>There is one required option in the `hosts` dict:| no
diff --git a/README-hostgroup.md b/README-hostgroup.md
index cedca7e2729b0fcb0b00b65450bd107e47133570..e97136ca48d773b5d12ee749f22e0ef35dfae33d 100644
--- a/README-hostgroup.md
+++ b/README-hostgroup.md
@@ -150,6 +150,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of hostgroup name strings. | no
 `description` | The hostgroup description string. | no
 `nomembers` | Suppress processing of membership attributes. (bool) | no
diff --git a/README-location.md b/README-location.md
index 3e9b7ef161ffb0e3080bc03bad92816edc8d55ab..203ba254ed9988655e06ca5bdca5ab88898ef8cc 100644
--- a/README-location.md
+++ b/README-location.md
@@ -81,6 +81,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `idnsname` | The list of location name strings. | yes
 `description` | The IPA location string | false
 `state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
diff --git a/README-permission.md b/README-permission.md
index c1cc539ad1c431b7f038f131a2112f2d1a2ea92b..950e6ff06809cea4e441556a3a1414b91c55196e 100644
--- a/README-permission.md
+++ b/README-permission.md
@@ -161,6 +161,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The permission name string. | yes
 `right` \| `ipapermright` | Rights to grant. It can be a list of one or more of `read`, `search`, `compare`, `write`, `add`, `delete`, and `all` default: `all` | no
 `attrs` | All attributes to which the permission applies. | no
diff --git a/README-privilege.md b/README-privilege.md
index ddb78a1ae2435ae718e6d422204c68be2dbca1d1..3b2537f8ec9ec25d318238bc1221024f52e32b71 100644
--- a/README-privilege.md
+++ b/README-privilege.md
@@ -133,6 +133,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin`. | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node. | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of privilege name strings. | yes
 `description` | Privilege description. | no
 `rename` \| `new_name` | Rename the privilege object. | no
diff --git a/README-pwpolicy.md b/README-pwpolicy.md
index f0b5d8853d508301fb1fb343efcef8376e77001b..94811596cf09492e1b7810c0689ae2e679f7b4b3 100644
--- a/README-pwpolicy.md
+++ b/README-pwpolicy.md
@@ -98,6 +98,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of pwpolicy name strings. If name is not given, `global_policy` will be used automatically. | no
 `maxlife` \| `krbmaxpwdlife` | Maximum password lifetime in days. (int) | no
 `minlife` \| `krbminpwdlife` | Minimum password lifetime in hours. (int) | no
diff --git a/README-role.md b/README-role.md
index 75248359e01bc622f7f2ed832949f1fb43fe2b03..fc915c2b6d9b301adc5dff666f2b774849e09b08 100644
--- a/README-role.md
+++ b/README-role.md
@@ -245,6 +245,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of role name strings. | yes
 `description` | A description for the role. | no
 `rename` \| `new_name` | Rename the role object. | no
diff --git a/README-selfservice.md b/README-selfservice.md
index ae6a8a3e4313e3f301460466c06e39d0d8969628..8bfa8352e0b8e03c6bf1ee78ca15df26492d17f2 100644
--- a/README-selfservice.md
+++ b/README-selfservice.md
@@ -138,6 +138,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `aciname` | The list of selfservice name strings. | yes
 `permission` \| `permissions` |  The permission to grant `read`, `read,write`, `write`]. Default is `write`. | no
 `attribute` \| `attrs` | The attribute list to which the selfservice applies. | no
diff --git a/README-server.md b/README-server.md
index b899cdaca02141e60b55018deb6717600c987f7b..66e62bb53188bb8416ede9e4900bf2081aa29dfa 100644
--- a/README-server.md
+++ b/README-server.md
@@ -230,6 +230,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of server name strings. | yes
 `location` \| `ipalocation_location` | The server location string. Only in state: present. "" for location reset. | no
 `service_weight` \| `ipaserviceweight` | Weight for server services. Type Values 0 to 65535, -1 for weight reset. Only in state: present. (int) | no
diff --git a/README-service.md b/README-service.md
index b6a014ed08eb5b2bc0aa52dcc6d5360cc44e1033..0cc6b8792a8f657f2a548f312474f0dae4a13e1c 100644
--- a/README-service.md
+++ b/README-service.md
@@ -291,6 +291,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `service` | The list of service name strings. | yes
 `certificate` \| `usercertificate` | Base-64 encoded service certificate. | no
 `pac_type` \| `ipakrbauthzdata` | Supported PAC type. It can be one of `MS-PAC`, `PAD`, or `NONE`. | no
diff --git a/README-sudocmd.md b/README-sudocmd.md
index e68fa01da2f8b36db9afc35214d6108752e97b63..2c30ddbf121d5d4e631911cba2a92b489da97cf0 100644
--- a/README-sudocmd.md
+++ b/README-sudocmd.md
@@ -83,6 +83,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `sudocmd` | The sudo command strings. | yes
 `description` | The command description string. | no
 `nomembers` | Suppress processing of membership attributes. (bool) | no
diff --git a/README-sudocmdgroup.md b/README-sudocmdgroup.md
index cca08c191d92e18b9fdf6ee610a4b734aec44d1a..e37f577aa08a94575b7d0a0674adb22c77fda5ab 100644
--- a/README-sudocmdgroup.md
+++ b/README-sudocmdgroup.md
@@ -123,6 +123,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of sudocmdgroup name strings. | no
 `description` | The sudocmdgroup description string. | no
 `nomembers` | Suppress processing of membership attributes. (bool) | no
diff --git a/README-sudorule.md b/README-sudorule.md
index b973c369eb7af2659e70bbd7f8bb7e7d3f0ace23..f7954489c7a0989e369259672f8123b2d258afad 100644
--- a/README-sudorule.md
+++ b/README-sudorule.md
@@ -120,6 +120,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` \| `cn` | The list of sudorule name strings. | yes
 `description` | The sudorule description string. | no
 `usercategory` \| `usercat` | User category the rule applies to. Choices: ["all", ""] | no
diff --git a/README-topology.md b/README-topology.md
index 84ab240d50166f57a05494ffcb1b49ebddfaf477..9391356b59fd3b68c76aaa95fe3fcb85f33a9f27 100644
--- a/README-topology.md
+++ b/README-topology.md
@@ -159,11 +159,12 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `suffix` | The topology suffix to be used, this can either be `domain`, `ca` or `domain+ca` | yes
 `name` \| `cn` | The topology segment name (cn) is the unique identifier for a segment. | no
 `left` \| `leftnode` | The left replication node string - an IPA server | no
 `right` \| `rightnode` | The right replication node string - an IPA server | no
-`direction` | The direction a segment will be reinitialized. It can either be `left-to-right` or `right-to-left` and only used with `state: reinitialized` | 
+`direction` | The direction a segment will be reinitialized. It can either be `left-to-right` or `right-to-left` and only used with `state: reinitialized` | no
 `state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `checked` or `reinitialized` | yes
 
 
@@ -176,6 +177,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `suffix` | The topology suffix to be used, this can either be `domain` or `ca` | yes
 `state` | The state to ensure. It can only be `verified` | yes
 
diff --git a/README-trust.md b/README-trust.md
index bc16bd3060aabbc271b4a69315dbd20c8d71a9c0..603303e1c4e5deafe4f2b68a6c9bc9f54d552be5 100644
--- a/README-trust.md
+++ b/README-trust.md
@@ -101,6 +101,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `realm` | The realm name string. | yes
 `admin` | Active Directory domain administrator string. | no
 `password` | Active Directory domain administrator's password string. | no
diff --git a/README-user.md b/README-user.md
index 0a7cc6a810f1348e1f58f1a83552376124b19918..7b88f5940a1e53c7ed967fc00c80d9eb25e94941 100644
--- a/README-user.md
+++ b/README-user.md
@@ -365,6 +365,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
 `name` | The list of user name strings. `name` with *user variables* or `users` containing *user variables* need to be used. | no
 **User variables** | Only used with `name` variable in the first level. | no
 `users` | The list of user dicts. Each `users` dict entry can contain **user variables**.<br>There is one required option in the `users` dict:| no
diff --git a/README-vault.md b/README-vault.md
index 3f5f989e9364bd30705835f4559d36f32357723e..545c343a595549ee6d264e7f771dc4bd2356a7c1 100644
--- a/README-vault.md
+++ b/README-vault.md
@@ -217,6 +217,7 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`ipaapi_context` | The context in which the module will execute. Currently only `client` is supported by this module, and use of `server` will raise a failure. | no
 `name` \| `cn` | The list of vault name strings. | yes
 `description` | The vault description string. | no
 `password` \| `vault_password` \| `ipavaultpassword` \| `old_password`| Vault password. | no
diff --git a/plugins/doc_fragments/ipamodule_base_docs.py b/plugins/doc_fragments/ipamodule_base_docs.py
index 22f2a1d1c8fbc2240326fb533b61cc8b48eedd66..4e8c126835ba7bb32c503fbf3508d38863c4ffb0 100644
--- a/plugins/doc_fragments/ipamodule_base_docs.py
+++ b/plugins/doc_fragments/ipamodule_base_docs.py
@@ -30,4 +30,11 @@ options:
   ipaadmin_password:
     description: The admin password.
     required: false
+  ipaapi_context:
+    description: |
+      The context in which the module will execute. Executing in a
+      server context is preferred. If not provided context will be
+      determined by the execution environment.
+    choices: ["server", "client"]
+    required: false
 """
diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
index fede14a5ee4744c923da233a08ecc2e029af1b0a..fe5268046829ca4690146c5dd76884e2e5d51184 100644
--- a/plugins/module_utils/ansible_freeipa_module.py
+++ b/plugins/module_utils/ansible_freeipa_module.py
@@ -107,6 +107,34 @@ else:
     except ImportError:
         from collections import Mapping  # pylint: disable=deprecated-class
 
+    # Try to import is_ipa_configured or use a fallback implementation.
+    try:
+        from ipalib.facts import is_ipa_configured
+    except ImportError:
+        try:
+            from ipaserver.install.installutils import is_ipa_configured
+        except ImportError:
+            from ipalib.install import sysrestore
+
+            def is_ipa_configured():
+                sstore = sysrestore.StateFile(paths.SYSRESTORE)
+
+                if sstore.has_state('installation'):
+                    return sstore.get_state('installation', 'complete')
+
+                fstore = sysrestore.FileStore(paths.SYSRESTORE)
+
+                IPA_MODULES = [  # pylint: disable=invalid-name
+                    'httpd', 'kadmin', 'dirsrv', 'pki-tomcatd', 'install',
+                    'krb5kdc', 'ntpd', 'named'
+                ]
+
+                for module in IPA_MODULES:
+                    if sstore.has_state(module):
+                        return True
+
+                return fstore.has_files()
+
     if six.PY3:
         unicode = str
 
@@ -179,18 +207,25 @@ else:
 
         `context` can be any of:
             * `server` (default)
-            * `ansible-freeipa`
-            * `cli_installer`
+            * `client`
         """
         env = Env()
         env._bootstrap()
         env._finalize_core(**dict(DEFAULT_CONFIG))
 
-        # available contexts are 'server', 'ansible-freeipa' and
-        # 'cli_installer'
-
+        # If not set, context will be based on current API context.
         if context is None:
-            context = 'server'
+            context = "server" if is_ipa_configured() else "client"
+
+        # Available contexts are 'server' and 'client'.
+        if context not in ["server", "client"]:
+            raise ValueError("Invalid execution context: %s" % (context))
+
+        # IPA uses 'cli' for a 'client' context, but 'client'
+        # provides a better user interface. Here we map the
+        # value if needed.
+        if context == "client":
+            context = "cli"
 
         api.bootstrap(context=context, debug=env.debug, log=None)
         api.finalize()
@@ -577,6 +612,9 @@ else:
         ipa_module_base_spec = dict(
             ipaadmin_principal=dict(type="str", default="admin"),
             ipaadmin_password=dict(type="str", required=False, no_log=True),
+            ipaapi_context=dict(
+                type="str", required=False, choices=["server", "client"],
+            ),
         )
 
         def __init__(self, *args, **kwargs):
@@ -604,6 +642,8 @@ else:
             # ipaadmin vars
             ipaadmin_principal = self.params_get("ipaadmin_principal")
             ipaadmin_password = self.params_get("ipaadmin_password")
+            if context is None:
+                context = self.params_get("ipaapi_context")
 
             ccache_dir = None
             ccache_name = None
@@ -1071,7 +1111,8 @@ else:
 
         def ipa_run(self):
             """Execute module actions."""
-            with self.ipa_connect():
+            ipaapi_context = self.ipa_params.get("ipaapi_context")
+            with self.ipa_connect(context=ipaapi_context):
                 self.check_ipa_params()
                 self.define_ipa_commands()
                 self._run_ipa_commands()
diff --git a/plugins/modules/ipadnsforwardzone.py b/plugins/modules/ipadnsforwardzone.py
index f6d4a24d635e8da4bbded7c320105447563f19ee..492a31732a928ff6a338d75769be0a2cd58bc6c6 100644
--- a/plugins/modules/ipadnsforwardzone.py
+++ b/plugins/modules/ipadnsforwardzone.py
@@ -160,6 +160,19 @@ def forwarder_list(forwarders):
     return fwd_list
 
 
+def fix_resource_data_types(resource):
+    """Fix resource data types."""
+    # When running in client context, some data might
+    # not come as a list, so we need to fix it before
+    # applying any modifications to it.
+    forwarders = resource["idnsforwarders"]
+    if isinstance(forwarders, str):
+        forwarders = [forwarders]
+    elif isinstance(forwarders, tuple):
+        forwarders = list(forwarders)
+    resource["idnsforwarders"] = forwarders
+
+
 def main():
     ansible_module = IPAAnsibleModule(
         argument_spec=dict(
@@ -288,6 +301,7 @@ def main():
                     continue
 
             else:   # existing_resource is not None
+                fix_resource_data_types(existing_resource)
                 if state != "absent":
                     if forwarders:
                         forwarders = list(
diff --git a/plugins/modules/ipadnszone.py b/plugins/modules/ipadnszone.py
index e2b8dba1048936b0d2554d51f15a1c40c0d6045e..30ceef271f3c872d79bf5bf8d8a97308c31ae434 100644
--- a/plugins/modules/ipadnszone.py
+++ b/plugins/modules/ipadnszone.py
@@ -405,7 +405,7 @@ class DNSZoneModule(FreeIPABaseModule):
             is_zone_active = False
         else:
             zone = response["result"]
-            is_zone_active = zone.get("idnszoneactive") == ["TRUE"]
+            is_zone_active = "TRUE" in zone.get("idnszoneactive")
 
         return zone, is_zone_active
 
diff --git a/plugins/modules/ipapermission.py b/plugins/modules/ipapermission.py
index 2dc6ab1cea590e791aec8669ddb63e6b818da73c..657d934ff7ff1f8f922c806e8f5aed7d77a79273 100644
--- a/plugins/modules/ipapermission.py
+++ b/plugins/modules/ipapermission.py
@@ -371,6 +371,10 @@ def main():
                     for _member, _member_change in check_members.items():
                         if _member_change is not None:
                             _res_list = res_find[_member]
+                            # if running in a client context, data may be
+                            # returned as a tuple instead of a list.
+                            if isinstance(_res_list, tuple):
+                                _res_list = list(_res_list)
                             _new_set = set(_res_list + _member_change)
                             if _new_set != set(_res_list):
                                 member_attrs[_member] = list(_new_set)
diff --git a/plugins/modules/ipavault.py b/plugins/modules/ipavault.py
index 7af6c353e7c2bb29b8ce96a2d0619820e9a53e4e..abd5eddffd25ff0b4838bc957217bf02377e0242 100644
--- a/plugins/modules/ipavault.py
+++ b/plugins/modules/ipavault.py
@@ -443,6 +443,11 @@ def check_parameters(  # pylint: disable=unused-argument
         password, password_file, public_key, public_key_file, private_key,
         private_key_file, vault_data, datafile_in, datafile_out, new_password,
         new_password_file):
+    if module.params_get("ipaapi_context") == "server":
+        module.fail_json(
+            msg="Context 'server' for ipavault not yet supported."
+        )
+
     invalid = []
     if state == "present":
         invalid = ['datafile_out']
@@ -718,7 +723,7 @@ def main():
     changed = False
     exit_args = {}
 
-    with ansible_module.ipa_connect(context='ansible-freeipa') as ccache_name:
+    with ansible_module.ipa_connect(context="client") as ccache_name:
         if ccache_name is not None:
             os.environ["KRB5CCNAME"] = ccache_name
 
diff --git a/tests/automember/test_automember.yml b/tests/automember/test_automember.yml
index 24b8fb320a844ba372f726c338fd7ceed99c30d1..4f1516df9cf34f6a492619c3b5f9d6b9b0697749 100644
--- a/tests/automember/test_automember.yml
+++ b/tests/automember/test_automember.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test automember
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
 
   tasks:
@@ -10,18 +10,21 @@
   - name: Ensure group testgroup is absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       state: absent
 
   - name: Ensure hostgroup testhostgroup is absent
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       state: absent
 
   - name: Ensure group automember rule testgroup is absent
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       state: absent
       automember_type: group
@@ -29,6 +32,7 @@
   - name: Ensure hostgroup automember rule testhostgroup is absent
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       state: absent
       automember_type: hostgroup
@@ -39,16 +43,19 @@
   - name: Ensure testgroup group is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
 
   - name: Ensure testhostgroup hostgroup is present
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
 
   - name: Ensure testgroup group automember rule is present
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       description: testgroup automember rule.
       automember_type: group
@@ -58,6 +65,7 @@
   - name: Ensure testgroup group automember rule is present again
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       description: testgroup automember rule.
       automember_type: group
@@ -67,6 +75,7 @@
   - name: Change testgroup group automember rule description
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       description: testgroup automember rule description.
       automember_type: group
@@ -76,6 +85,7 @@
   - name: Ensure testgroup group automember rule has conditions
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       automember_type: group
       inclusive:
@@ -92,6 +102,7 @@
   - name: Ensure testgroup group automember rule has conditions again
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       automember_type: group
       inclusive:
@@ -108,6 +119,7 @@
   - name: Add testgroup group automember rule member condition
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       automember_type: group
       action: member
@@ -120,6 +132,7 @@
   - name: Ensure testgroup group automember rule has conditions
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       automember_type: group
       inclusive:
@@ -138,6 +151,7 @@
   - name: Remove testgroup group automember rule member condition
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       automember_type: group
       action: member
@@ -151,6 +165,7 @@
   - name: Ensure testgroup group automember rule has conditions again
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       automember_type: group
       inclusive:
@@ -168,6 +183,7 @@
     ipaautomember:
       ipaadmin_principal: admin
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       automember_type: group
       inclusive:
@@ -182,6 +198,7 @@
     ipaautomember:
       ipaadmin_principal: admin
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       automember_type: group
       exclusive:
@@ -195,6 +212,7 @@
   - name: Ensure testhostgroup hostgroup automember rule is present
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       description: testhostgroup automember rule
       automember_type: hostgroup
@@ -204,6 +222,7 @@
   - name: Ensure testhostgroup hostgroup automember rule is present again
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       description: testhostgroup automember rule
       automember_type: hostgroup
@@ -213,6 +232,7 @@
   - name: Change testhostgroup hostgroup automember rule description
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       description: testhostgroup test automember rule
       automember_type: hostgroup
@@ -222,6 +242,7 @@
   - name: Ensure testhostgroup hostgroup automember rule has conditions
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       automember_type: hostgroup
       inclusive:
@@ -238,6 +259,7 @@
   - name: Ensure testhostgroup hostgroup automember rule has conditions again
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       automember_type: hostgroup
       inclusive:
@@ -254,6 +276,7 @@
   - name: Add testhostgroup hostgroup automember rule member condition
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       automember_type: hostgroup
       action: member
@@ -266,6 +289,7 @@
   - name: Ensure testhostgroup hostgroup automember rule has conditions
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       automember_type: hostgroup
       inclusive:
@@ -284,6 +308,7 @@
   - name: Remove testhostgroup hostgroup automember rule member condition
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       automember_type: hostgroup
       action: member
@@ -297,6 +322,7 @@
   - name: Ensure testhostgroup hostgroup automember rule has conditions
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       automember_type: hostgroup
       inclusive:
@@ -315,6 +341,7 @@
     ipaautomember:
       ipaadmin_principal: admin
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       automember_type: hostgroup
       inclusive:
@@ -329,6 +356,7 @@
     ipaautomember:
       ipaadmin_principal: admin
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       automember_type: hostgroup
       exclusive:
@@ -344,18 +372,21 @@
   - name: Ensure group testgroup is absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup
       state: absent
 
   - name: Ensure hostgroup testhostgroup is absent
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup
       state: absent
 
   - name: Ensure group automember rule testgroup is absent
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       automember_type: group
       name: testgroup
       state: absent
@@ -363,6 +394,7 @@
   - name: Ensure hostgroup automember rule testhostgroup is absent
     ipaautomember:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       automember_type: hostgroup
       name: testhostgroup
       state: absent
diff --git a/tests/automember/test_automember_client_context.yml b/tests/automember/test_automember_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a3cd68d20f671115e1ca99ee710efad6011f6def
--- /dev/null
+++ b/tests/automember/test_automember_client_context.yml
@@ -0,0 +1,38 @@
+---
+- name: Test automember
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipaautomember:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+      state: rebuild
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test automember using client context, in client host.
+  import_playbook: test_automember.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test automember using client context, in server host.
+  import_playbook: test_automember.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/automount/test_automountlocation.yml b/tests/automount/test_automountlocation.yml
index 1eb11a65de10c2288f4c5fa83f77afed24eefead..7e6c3abb9788c18a4b6ae6fd0c772c8f3e8a8fd3 100644
--- a/tests/automount/test_automountlocation.yml
+++ b/tests/automount/test_automountlocation.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test automountlocation
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -8,6 +8,7 @@
   - name: ensure automountlocation TestLocations are absent before testing
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - TestLocation_01
       - TestLocation_02
@@ -16,6 +17,7 @@
   - name: ensure empty automountlocation does nothing
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: []
       state: present
     register: result
@@ -24,6 +26,7 @@
   - name: ensure empty automountlocation does nothing on absent
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: []
       state: absent
     register: result
@@ -32,6 +35,7 @@
   - name: ensure automountlocation TestLocation is present
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: TestLocation_01
       state: present
     register: result
@@ -40,6 +44,7 @@
   - name: ensure automountlocation TestLocation is present again
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: TestLocation_01
       state: present
     register: result
@@ -48,6 +53,7 @@
   - name: ensure automountlocation TestLocation is absent
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: TestLocation_01
       state: absent
     register: result
@@ -56,6 +62,7 @@
   - name: ensure automountlocation TestLocation is absent again
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: TestLocation_01
       state: absent
     register: result
@@ -64,6 +71,7 @@
   - name: ensure a list of automountlocations are present
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - TestLocation_01
       - TestLocation_02
@@ -74,6 +82,7 @@
   - name: ensure a list of automountlocations exist
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - TestLocation_01
       - TestLocation_02
@@ -84,6 +93,7 @@
   - name: ensure a list of automountlocations are absent
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - TestLocation_01
       - TestLocation_02
@@ -94,6 +104,7 @@
   - name: ensure multiple automountlocations are absent
     ipaautomountlocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - TestLocation_01
       - TestLocation_02
diff --git a/tests/automount/test_automountlocation_client_context.yml b/tests/automount/test_automountlocation_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ac5341408d6068e3a38af10a8000ec40ca85e6a5
--- /dev/null
+++ b/tests/automount/test_automountlocation_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test automountlocation
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipaautomountlocation:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test automountlocation using client context, in client host.
+  import_playbook: test_automountlocation.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test automountlocation using client context, in server host.
+  import_playbook: test_automountlocation.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/config/test_config.yml b/tests/config/test_config.yml
index 54a572ebd7bcc1b17791f5833e92d593ddc2cd78..01c1913f7192500a9498e6b62f695d7b433224c6 100644
--- a/tests/config/test_config.yml
+++ b/tests/config/test_config.yml
@@ -1,6 +1,6 @@
 ---
 - name: Playbook to handle server configuration
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -12,6 +12,7 @@
       - name: return current values of the global configuration options
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
         register: previousconfig
 
       - debug:
@@ -21,94 +22,112 @@
       - name: create test group
         ipagroup:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: somedefaultgroup
 
       - name: Ensure the default e-mail domain is ipa.test.
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           emaildomain: ipa.test
 
       - name: set default shell to '/bin/sh'
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           defaultshell: /bin/sh
 
       - name: set default group
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           defaultgroup: ipausers
 
       - name: set default home directory
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           homedirectory: /home
 
       - name: clear pac-type
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           pac_type: ""
 
       - name: set maxhostname to 255
         block:
           - ipaconfig:
               ipaadmin_password: SomeADMINpassword
+              ipaapi_context: "{{ ipa_context | default(omit) }}"
               maxhostname: 255
         when: ipa_version is version('4.8.0', '>=')
 
       - name: set maxusername to 45
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           maxusername: 45
 
       - name: set pwdexpnotify to 0
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           pwdexpnotify: 0
 
       - name: set searchrecordslimit to 10
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           searchrecordslimit: 10
 
       - name: set searchtimelimit to 1
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           searchtimelimit: 1
 
       - name: clear configstring
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           configstring: ""
 
       - name: set configstring to AllowNThash
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           configstring: 'KDC:Disable Lockout'
 
       - name: set selinuxusermapdefault
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           selinuxusermapdefault: "staff_u:s0-s0:c0.c1023"
 
       - name: set selinuxusermaporder
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           selinuxusermaporder: 'user_u:s0$staff_u:s0-s0:c0.c1023'
 
       - name: set usersearch to `uid`
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           usersearch: uid
 
       - name: set groupsearch to `cn`
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           groupsearch: cn
 
       # tests
       - name: Ensure the default e-mail domain is somedomain.test.
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           emaildomain: somedomain.test
         register: result
         failed_when: not result.changed or result.failed
@@ -116,6 +135,7 @@
       - name: Ensure the default e-mail domain is somedomain.test, again.
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           emaildomain: somedomain.test
         register: result
         failed_when: result.changed or result.failed
@@ -123,6 +143,7 @@
       - name: set default shell to '/bin/someshell'
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           defaultshell: /bin/someshell
         register: result
         failed_when: not result.changed or result.failed
@@ -130,6 +151,7 @@
       - name: set default shell to '/bin/someshell', again.
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           defaultshell: /bin/someshell
         register: result
         failed_when: result.changed or result.failed
@@ -137,6 +159,7 @@
       - name: set default group
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           defaultgroup: somedefaultgroup
         register: result
         failed_when: not result.changed or result.failed
@@ -144,6 +167,7 @@
       - name: set default group, again
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           defaultgroup: somedefaultgroup
         register: result
         failed_when: result.changed or result.failed
@@ -151,6 +175,7 @@
       - name: set default home directory
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           homedirectory: /Users
         register: result
         failed_when: not result.changed or result.failed
@@ -158,6 +183,7 @@
       - name: set default home directory, again
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           homedirectory: /Users
         register: result
         failed_when: result.changed or result.failed
@@ -165,6 +191,7 @@
       - name: set pac-type
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           pac_type: "nfs:NONE"
         register: result
         failed_when: not result.changed or result.failed
@@ -172,6 +199,7 @@
       - name: set pac-type, again.
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           pac_type: "nfs:NONE"
         register: result
         failed_when: result.changed or result.failed
@@ -179,6 +207,7 @@
       - name: set maxusername to 33
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           maxusername: 33
         register: result
         failed_when: not result.changed or result.failed
@@ -186,6 +215,7 @@
       - name: set maxusername to 33, again.
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           maxusername: 33
         register: result
         failed_when: result.changed or result.failed
@@ -194,12 +224,14 @@
         block:
           - ipaconfig:
               ipaadmin_password: SomeADMINpassword
+              ipaapi_context: "{{ ipa_context | default(omit) }}"
               maxhostname: 77
             register: result
             failed_when: not result.changed or result.failed
 
           - ipaconfig:
               ipaadmin_password: SomeADMINpassword
+              ipaapi_context: "{{ ipa_context | default(omit) }}"
               maxhostname: 77
             register: result
             failed_when: result.changed or result.failed
@@ -208,6 +240,7 @@
       - name: set pwdexpnotify to 17
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           pwdexpnotify: 17
         register: result
         failed_when: not result.changed or result.failed
@@ -215,6 +248,7 @@
       - name: set pwdexpnotify to 17, again
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           pwdexpnotify: 17
         register: result
         failed_when: result.changed or result.failed
@@ -222,6 +256,7 @@
       - name: set searchrecordslimit to -1
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           searchrecordslimit: -1
         register: result
         failed_when: not result.changed or result.failed
@@ -229,6 +264,7 @@
       - name: set searchrecordslimit to -1, again.
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           searchrecordslimit: -1
         register: result
         failed_when: result.changed or result.failed
@@ -236,6 +272,7 @@
       - name: set searchtimelimit to 12345
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           searchtimelimit: 12345
         register: result
         failed_when: not result.changed or result.failed
@@ -243,6 +280,7 @@
       - name: set searchtimelimit to 12345, again.
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           searchtimelimit: 12345
         register: result
         failed_when: result.changed or result.failed
@@ -250,6 +288,7 @@
       - name: change enable_migration
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           enable_migration: '{{ not (previousconfig.config.enable_migration | bool) }}'
         register: result
         failed_when: not result.changed or result.failed
@@ -257,6 +296,7 @@
       - name: change enable_migration, again
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           enable_migration: '{{ not (previousconfig.config.enable_migration | bool) }}'
         register: result
         failed_when: result.changed or result.failed
@@ -264,6 +304,7 @@
       - name: set configstring to AllowNThash
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           configstring: AllowNThash
         register: result
         failed_when: not result.changed or result.failed
@@ -271,6 +312,7 @@
       - name: set configstring to AllowNThash, again.
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           configstring: AllowNThash
         register: result
         failed_when: result.changed or result.failed
@@ -278,6 +320,7 @@
       - name: set selinuxusermaporder
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           selinuxusermaporder: 'user_u:s0$staff_u:s0-s0:c0.c1023$sysadm_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'
         register: result
         failed_when: not result.changed or result.failed
@@ -285,6 +328,7 @@
       - name: set selinuxusermaporder, again
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           selinuxusermaporder: 'user_u:s0$staff_u:s0-s0:c0.c1023$sysadm_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'
         register: result
         failed_when: result.changed or result.failed
@@ -292,6 +336,7 @@
       - name: set selinuxusermapdefault
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           selinuxusermapdefault: 'user_u:s0'
         register: result
         failed_when: not result.changed or result.failed
@@ -299,6 +344,7 @@
       - name: set selinuxusermapdefault, again
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           selinuxusermapdefault: 'user_u:s0'
         register: result
         failed_when: result.changed or result.failed
@@ -306,6 +352,7 @@
       - name: set groupsearch to `description`
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           groupsearch: description
         register: result
         failed_when: not result.changed or result.failed
@@ -313,6 +360,7 @@
       - name: set groupsearch to `gidNumber`, again
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           groupsearch: description
         register: result
         failed_when: result.changed or result.failed
@@ -320,6 +368,7 @@
       - name: set usersearch to `uidNumber`
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           usersearch: uidNumber
         register: result
         failed_when: not result.changed or result.failed
@@ -327,6 +376,7 @@
       - name: set usersearch to `uidNumber`, again
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           usersearch: uidNumber
         register: result
         failed_when: result.changed or result.failed
@@ -334,6 +384,7 @@
       - name: reset changed fields
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           maxusername: '{{ previousconfig.config.maxusername | default(32) | int }}'
           homedirectory: '{{ previousconfig.config.homedirectory | default(omit) }}'
           defaultshell: '{{ previousconfig.config.defaultshell | default(omit) }}'
@@ -361,12 +412,14 @@
         block:
           - ipaconfig:
               ipaadmin_password: SomeADMINpassword
+              ipaapi_context: "{{ ipa_context | default(omit) }}"
               maxhostname: '{{ previousconfig.config.maxhostname | default(omit) }}'
         when: ipa_version is version('4.8.0', '>=')
 
       - name: reset changed fields, again
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           maxusername: '{{ previousconfig.config.maxusername | default(omit) | int }}'
           homedirectory: '{{ previousconfig.config.homedirectory | default(omit) }}'
           defaultshell: '{{ previousconfig.config.defaultshell | default(omit) }}'
@@ -394,6 +447,7 @@
         block:
           - ipaconfig:
               ipaadmin_password: SomeADMINpassword
+              ipaapi_context: "{{ ipa_context | default(omit) }}"
               maxhostname: '{{ previousconfig.config.maxhostname | default(omit) }}'
         when: ipa_version is version('4.8.0', '>=')
 
@@ -401,6 +455,7 @@
       - name: Set fields to IPA default, due to error
         ipaconfig:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           maxusername: '{{ previousconfig.config.maxusername | default(omit) | int }}'
           homedirectory: '{{ previousconfig.config.homedirectory | default(omit) }}'
           defaultshell: '{{ previousconfig.config.defaultshell | default(omit) }}'
@@ -431,5 +486,6 @@
       - name: cleanup test group
         ipagroup:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: somedefaultgroup
           state: absent
diff --git a/tests/config/test_config_client_context.yml b/tests/config/test_config_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..589105f8f60788784b6cadded3272bb793e752f5
--- /dev/null
+++ b/tests/config/test_config_client_context.yml
@@ -0,0 +1,36 @@
+---
+- name: Test config
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipaconfig:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test config using client context, in client host.
+  import_playbook: test_config.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test config using client context, in server host.
+  import_playbook: test_config.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/delegation/test_delegation.yml b/tests/delegation/test_delegation.yml
index 4f609b8a19b14d64d862ae800f777e4973b5a415..6bd0df7fdc2ad1972435ffa26b03aba6fde1ca70 100644
--- a/tests/delegation/test_delegation.yml
+++ b/tests/delegation/test_delegation.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test delegation
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
 
   tasks:
@@ -10,12 +10,14 @@
   - name: Ensure test groups are absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: managers,managers2,employees,employees2
       state: absent
 
   - name: Ensure delegation "basic manager attributes" is absent
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       state: absent
 
@@ -24,21 +26,25 @@
   - name: Ensure test group managers is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: managers
 
   - name: Ensure test group managers2 is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: managers2
 
   - name: Ensure test group employees is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: employees
 
   - name: Ensure test group employees2 is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: employees2
 
   # TESTS
@@ -46,6 +52,7 @@
   - name: Ensure delegation "basic manager attributes" is present
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       permission: read
       attribute:
@@ -58,6 +65,7 @@
   - name: Ensure delegation "basic manager attributes" is present again
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       permission: read
       attribute:
@@ -70,6 +78,7 @@
   - name: Ensure delegation "basic manager attributes" is present with different attribute employeetype
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       permission: read
       attribute:
@@ -82,6 +91,7 @@
   - name: Ensure delegation "basic manager attributes" is present with different attribute employeetype again
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       permission: read
       attribute:
@@ -94,6 +104,7 @@
   - name: Ensure delegation "basic manager attributes" member attribute departmentnumber is present
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       attribute:
       - departmentnumber
@@ -104,6 +115,7 @@
   - name: Ensure delegation "basic manager attributes" member attribute departmentnumber is present again
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       attribute:
       - departmentnumber
@@ -114,6 +126,7 @@
   - name: Ensure delegation "basic manager attributes" member attributes employeetype and employeenumber are present
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       attribute:
       - employeetype
@@ -125,6 +138,7 @@
   - name: Ensure delegation "basic manager attributes" member attributes employeetype and employeenumber are present again
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       attribute:
       - employeetype
@@ -136,6 +150,7 @@
   - name: Ensure delegation "basic manager attributes" member attributes employeenumber and employeetype are absent
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       attribute:
       - employeenumber
@@ -148,6 +163,7 @@
   - name: Ensure delegation "basic manager attributes" member attributes employeenumber and employeetype are absent again
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       attribute:
       - employeenumber
@@ -162,6 +178,7 @@
   - name: Ensure delegation "basic manager attributes" is present with different read,write permission
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       permission: read,write
       attribute:
@@ -174,6 +191,7 @@
   - name: Ensure delegation "basic manager attributes" is present with different read,write permission again
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       permission: read,write
       attribute:
@@ -186,6 +204,7 @@
   - name: Ensure delegation "basic manager attributes" is present with different group managers2
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       group: managers2
     register: result
@@ -194,6 +213,7 @@
   - name: Ensure delegation "basic manager attributes" is present with different group managers2 again
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       group: managers2
     register: result
@@ -202,6 +222,7 @@
   - name: Ensure delegation "basic manager attributes" is present with different membergroup employees2
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       membergroup: employees2
     register: result
@@ -210,6 +231,7 @@
   - name: Ensure delegation "basic manager attributes" is present with different membergroup employees2 again
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       membergroup: employees2
     register: result
@@ -218,6 +240,7 @@
   - name: Ensure delegation "basic manager attributes" fails with bad permission read,read
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       permission: read,read
     register: result
@@ -226,6 +249,7 @@
   - name: Ensure delegation "basic manager attributes" fails with bad permission read,write,write
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       permission: read,write,write
     register: result
@@ -234,6 +258,7 @@
   - name: Ensure delegation "basic manager attributes" fails with bad attribute businesscategory,businesscategory
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       attribute:
       - businesscategory
@@ -246,11 +271,13 @@
   - name: Ensure delegation "basic manager attributes" is absent
     ipadelegation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "basic manager attributes"
       state: absent
 
   - name: Ensure test groups are absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: managers,managers2,employees,employees2
       state: absent
diff --git a/tests/delegation/test_delegation_client_context.yml b/tests/delegation/test_delegation_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8756897d3005d3ab0857eab1afa9de86fc696545
--- /dev/null
+++ b/tests/delegation/test_delegation_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test delegation
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipadelegation:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test delegation using client context, in client host.
+  import_playbook: test_delegation.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test delegation using client context, in server host.
+  import_playbook: test_delegation.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/dnsconfig/test_dnsconfig.yml b/tests/dnsconfig/test_dnsconfig.yml
index 3a69bea81dceb72345bbb799e21ba41f1d4d8ad8..b416a0d1762db698a4d6a111a8d80a3806599ea6 100644
--- a/tests/dnsconfig/test_dnsconfig.yml
+++ b/tests/dnsconfig/test_dnsconfig.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test dnsconfig
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: true
 
@@ -9,6 +9,7 @@
   - name: Ensure forwarders are absent.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
         - ip_address: 8.8.4.4
@@ -21,6 +22,7 @@
   - name: Set config to invalid IPv4.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 1.2.3.500
     register: result
@@ -29,6 +31,7 @@
   - name: Set config to invalid IP.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 1.in.va.lid
     register: result
@@ -37,6 +40,7 @@
   - name: Set config to invalid IPv6.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: fd00::invalid
     register: result
@@ -45,6 +49,7 @@
   - name: Set dnsconfig.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
         - ip_address: 8.8.4.4
@@ -58,6 +63,7 @@
   - name: Set dnsconfig, with the same values.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
         - ip_address: 8.8.4.4
@@ -71,6 +77,7 @@
   - name: Ensure forwarder is absent.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
       state: absent
@@ -80,6 +87,7 @@
   - name: Ensure forwarder is absent, again.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
       state: absent
@@ -89,6 +97,7 @@
   - name: Disable global forwarders.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forward_policy: none
     register: result
     failed_when: not result.changed or result.failed
@@ -96,6 +105,7 @@
   - name: Disable global forwarders, again.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forward_policy: none
     register: result
     failed_when: result.changed or result.failed
@@ -103,6 +113,7 @@
   - name: Re-enable global forwarders.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forward_policy: first
     register: result
     failed_when: not result.changed or result.failed
@@ -110,6 +121,7 @@
   - name: Re-enable global forwarders, again.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forward_policy: first
     register: result
     failed_when: result.changed or result.failed
@@ -117,6 +129,7 @@
   - name: Disable PTR record synchronization.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       allow_sync_ptr: no
     register: result
     failed_when: not result.changed or result.failed
@@ -124,6 +137,7 @@
   - name: Disable PTR record synchronization, again.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       allow_sync_ptr: no
     register: result
     failed_when: result.changed or result.failed
@@ -131,6 +145,7 @@
   - name: Re-enable PTR record synchronization.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       allow_sync_ptr: yes
     register: result
     failed_when: not result.changed or result.failed
@@ -138,6 +153,7 @@
   - name: Re-enable PTR record synchronization, again.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       allow_sync_ptr: yes
     register: result
     failed_when: result.changed or result.failed
@@ -145,6 +161,7 @@
   - name: Ensure all forwarders are absent.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
         - ip_address: 8.8.4.4
@@ -158,6 +175,7 @@
   - name: Ensure all forwarders are absent, again.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
         - ip_address: 8.8.4.4
@@ -171,6 +189,7 @@
   - name: Ensure forwarders are absent.
     ipadnsconfig:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       forwarders:
         - ip_address: 8.8.8.8
         - ip_address: 8.8.4.4
diff --git a/tests/dnsconfig/test_dnsconfig_client_context.yml b/tests/dnsconfig/test_dnsconfig_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6db3b62a86eff46213358cea0cf7846386397e5d
--- /dev/null
+++ b/tests/dnsconfig/test_dnsconfig_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test dnsconfig
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipadnsconfig:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      forward_policy: none
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test dnsconfig using client context, in client host.
+  import_playbook: test_dnsconfig.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test dnsconfig using client context, in server host.
+  import_playbook: test_dnsconfig.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/dnsforwardzone/test_dnsforwardzone.yml b/tests/dnsforwardzone/test_dnsforwardzone.yml
index b9569faa4c0b643ccfe425e1debcf61fb9fb69d6..260829ff576607e021da2fb91784ed07fc00f5b1 100644
--- a/tests/dnsforwardzone/test_dnsforwardzone.yml
+++ b/tests/dnsforwardzone/test_dnsforwardzone.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test dnsforwardzone
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -8,6 +8,7 @@
   - name: ensure test forwardzones are absent
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - example.com
       - newfailzone.com
@@ -16,6 +17,7 @@
   - name: ensure forwardzone example.com is created
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       forwarders:
@@ -28,6 +30,7 @@
   - name: ensure forwardzone example.com is present again
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       forwarders:
@@ -40,6 +43,7 @@
   - name: ensure forwardzone example.com has two forwarders
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       forwarders:
@@ -54,6 +58,7 @@
   - name: ensure forwardzone example.com has one forwarder again
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       forwarders:
         - ip_address: 8.8.8.8
@@ -66,6 +71,7 @@
   - name: skip_overlap_check can only be set on creation so change nothing
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       forwarders:
         - ip_address: 8.8.8.8
@@ -78,6 +84,7 @@
   - name: ensure forwardzone example.com is absent.
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: absent
     register: result
@@ -86,6 +93,7 @@
   - name: ensure forwardzone example.com is absent, again.
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: absent
     register: result
@@ -94,6 +102,7 @@
   - name: change all the things at once
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       forwarders:
@@ -109,6 +118,7 @@
   - name: change zone forward policy
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       forwardpolicy: first
     register: result
@@ -117,6 +127,7 @@
   - name: change zone forward policy, again
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       forwardpolicy: first
     register: result
@@ -125,6 +136,7 @@
   - name: ensure forwardzone example.com is absent.
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: absent
     register: result
@@ -133,6 +145,7 @@
   - name: ensure forwardzone example.com is absent, again.
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: absent
     register: result
@@ -141,6 +154,7 @@
   - name: ensure forwardzone example.com is created with minimal args
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       skip_overlap_check: true
@@ -152,6 +166,7 @@
   - name: ensure forwardzone example.com is created with minimal args, again
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       skip_overlap_check: true
@@ -163,6 +178,7 @@
   - name: add a forwarder to any existing ones
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       forwarders:
@@ -175,6 +191,7 @@
   - name: add a forwarder to any existing ones, again
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       forwarders:
@@ -187,6 +204,7 @@
   - name: check the list of forwarders is what we expect
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       forwarders:
@@ -200,6 +218,7 @@
   - name: remove a single forwarder
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: absent
       name: example.com
       forwarders:
@@ -211,6 +230,7 @@
   - name: remove a single forwarder, again
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: absent
       name: example.com
       forwarders:
@@ -222,6 +242,7 @@
   - name: check the list of forwarders is what we expect now
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       forwarders:
@@ -234,6 +255,7 @@
   - name: Add a permission for per-forward zone access delegation.
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       permission: yes
       action: member
@@ -243,6 +265,7 @@
   - name: Add a permission for per-forward zone access delegation, again.
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       permission: yes
       action: member
@@ -252,6 +275,7 @@
   - name: Remove a permission for per-forward zone access delegation.
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       permission: no
       action: member
@@ -261,6 +285,7 @@
   - name: Remove a permission for per-forward zone access delegation, again.
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       permission: no
       action: member
@@ -270,6 +295,7 @@
   - name: disable the forwarder
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: disabled
     register: result
@@ -278,6 +304,7 @@
   - name: disable the forwarder again
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: disabled
     register: result
@@ -286,6 +313,7 @@
   - name: enable the forwarder
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: enabled
     register: result
@@ -294,6 +322,7 @@
   - name: enable the forwarder, again
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: enabled
     register: result
@@ -302,12 +331,14 @@
   - name: ensure forwardzone example.com is absent again
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: absent
 
   - name: try to create a new forwarder with action=member
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: present
       name: example.com
       forwarders:
@@ -321,6 +352,7 @@
   - name: try to create a new forwarder with disabled state
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: example.com
       state: disabled
     register: result
@@ -329,6 +361,7 @@
   - name: Ensure forwardzone is not added without forwarders, with correct message.
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: newfailzone.com
     register: result
     failed_when: not result.failed or "No forwarders specified" not in result.msg
@@ -336,6 +369,7 @@
   - name: ensure forwardzone example.com is absent - tidy up
     ipadnsforwardzone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - example.com
       - newfailzone.com
diff --git a/tests/dnsforwardzone/test_dnsforwardzone_client_context.yml b/tests/dnsforwardzone/test_dnsforwardzone_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..23b536e21bb03f40395bbdd0af2d53ad563096ef
--- /dev/null
+++ b/tests/dnsforwardzone/test_dnsforwardzone_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test dnsforwardzone
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipadnsforwardzone:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test dnsforwardzone using client context, in client host.
+  import_playbook: test_dnsforwardzone.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test dnsforwardzone using client context, in server host.
+  import_playbook: test_dnsforwardzone.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/dnsrecord/env_cleanup.yml b/tests/dnsrecord/env_cleanup.yml
index c5890fa0ba33db6a6ed280e366f9bf09c01d8564..b74aca35090bc07ddf5b0b0ae7b91a3f3f556554 100644
--- a/tests/dnsrecord/env_cleanup.yml
+++ b/tests/dnsrecord/env_cleanup.yml
@@ -1,135 +1,144 @@
 ---
   # Cleanup tasks.
-   - name: Ensure that dns records are absent
-     ipadnsrecord:
-       ipaadmin_password: SomeADMINpassword
-       zone_name: "{{ testzone }}"
-       del_all: yes
-       name:
-       - host01
-       - host02
-       - host03
-       - host04
-       - _ftp._tcp
-       - _sip._udp
-       state: absent
+  - name: Ensure that dns records are absent
+    ipadnsrecord:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      zone_name: "{{ testzone }}"
+      del_all: yes
+      name:
+      - host01
+      - host02
+      - host03
+      - host04
+      - _ftp._tcp
+      - _sip._udp
+      state: absent
 
-   - name: Ensure that dns reverse ipv6 records are absent
-     ipadnsrecord:
-       ipaadmin_password: SomeADMINpassword
-       zone_name: ip6.arpa.
-       del_all: yes
-       name:
-       - 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
-       - 1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
-       - 1.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
-       - 4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
-       - 4.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
-       - 4.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
-       state: absent
+  - name: Ensure that dns reverse ipv6 records are absent
+    ipadnsrecord:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      zone_name: ip6.arpa.
+      del_all: yes
+      name:
+      - 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
+      - 1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
+      - 1.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
+      - 4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
+      - 4.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
+      - 4.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
+      state: absent
 
-   - name: Ensure that dns reverse ipv6 records are absent (workaround)
-     ipadnsrecord:
-       ipaadmin_password: SomeADMINpassword
-       zone_name: "{{ zone_ipv6_reverse_workaround }}"
-       del_all: yes
-       name:
-       - 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
-       - 1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
-       - 1.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
-       - 4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
-       - 4.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
-       - 4.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
-       state: absent
+  - name: Ensure that dns reverse ipv6 records are absent (workaround)
+    ipadnsrecord:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      zone_name: "{{ zone_ipv6_reverse_workaround }}"
+      del_all: yes
+      name:
+      - 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
+      - 1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
+      - 1.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
+      - 4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
+      - 4.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
+      - 4.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
+      state: absent
 
-   - name: Ensure that dns reverse records are absent
-     ipadnsrecord:
-       ipaadmin_password: SomeADMINpassword
-       zone_name: "{{ zone_prefix_reverse_24 }}"
-       name:
-       - "101"
-       - "102"
-       - "103"
-       - "104"
-       - "111"
-       - "112"
-       - "113"
-       - "114"
-       - "121"
-       - "122"
-       - "123"
-       - "124"
-       del_all: yes
-       state: absent
+  - name: Ensure that dns reverse records are absent
+    ipadnsrecord:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      zone_name: "{{ zone_prefix_reverse_24 }}"
+      name:
+      - "101"
+      - "102"
+      - "103"
+      - "104"
+      - "111"
+      - "112"
+      - "113"
+      - "114"
+      - "121"
+      - "122"
+      - "123"
+      - "124"
+      del_all: yes
+      state: absent
 
-   - name: Ensure that dns reverse records are absent (workaround 1)
-     ipadnsrecord:
-       ipaadmin_password: SomeADMINpassword
-       zone_name: "{{ zone_prefix_reverse_16 }}"
-       name:
-       - "101.122"
-       - "102.122"
-       - "103.122"
-       - "104.122"
-       - "111.122"
-       - "112.122"
-       - "113.122"
-       - "114.122"
-       - "121.122"
-       - "122.122"
-       - "123.122"
-       - "124.122"
-       del_all: yes
-       state: absent
+  - name: Ensure that dns reverse records are absent (workaround 1)
+    ipadnsrecord:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      zone_name: "{{ zone_prefix_reverse_16 }}"
+      name:
+      - "101.122"
+      - "102.122"
+      - "103.122"
+      - "104.122"
+      - "111.122"
+      - "112.122"
+      - "113.122"
+      - "114.122"
+      - "121.122"
+      - "122.122"
+      - "123.122"
+      - "124.122"
+      del_all: yes
+      state: absent
 
-   - name: Ensure that dns reverse records are absent (workaround 2)
-     ipadnsrecord:
-       ipaadmin_password: SomeADMINpassword
-       zone_name: "{{ zone_prefix_reverse_8 }}"
-       name:
-       - "168.101.122"
-       - "168.102.122"
-       - "168.103.122"
-       - "168.104.122"
-       - "168.111.122"
-       - "168.112.122"
-       - "168.113.122"
-       - "168.114.122"
-       - "168.121.122"
-       - "168.122.122"
-       - "168.123.122"
-       - "168.124.122"
-       del_all: yes
-       state: absent
+  - name: Ensure that dns reverse records are absent (workaround 2)
+    ipadnsrecord:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      zone_name: "{{ zone_prefix_reverse_8 }}"
+      name:
+      - "168.101.122"
+      - "168.102.122"
+      - "168.103.122"
+      - "168.104.122"
+      - "168.111.122"
+      - "168.112.122"
+      - "168.113.122"
+      - "168.114.122"
+      - "168.121.122"
+      - "168.122.122"
+      - "168.123.122"
+      - "168.124.122"
+      del_all: yes
+      state: absent
 
-   - name: Ensure that "{{ safezone }}" dns records are absent
-     ipadnsrecord:
-       ipaadmin_password: SomeADMINpassword
-       zone_name: "{{ safezone }}"
-       records:
-       - name: iron01
-         del_all: yes
-       state: absent
+  - name: Ensure that "{{ safezone }}" dns records are absent
+    ipadnsrecord:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      zone_name: "{{ safezone }}"
+      records:
+      - name: iron01
+        del_all: yes
+      state: absent
 
-   - name: Ensure that NS record for "{{ safezone }}" is absent
-     ipadnsrecord:
-       ipaadmin_password: SomeADMINpassword
-       name: iron01
-       zone_name: "{{ safezone }}"
-       ns_rec: iron01
-       state: absent
+  - name: Ensure that NS record for "{{ safezone }}" is absent
+    ipadnsrecord:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      name: iron01
+      zone_name: "{{ safezone }}"
+      ns_rec: iron01
+      state: absent
 
-   - name: Ensure DNS testing zones are absent.
-     ipadnszone:
-       ipaadmin_password: SomeADMINpassword
-       name: "{{ item }}"
-       state: absent
-     with_items:
-       - "{{ zone_prefix_reverse }}"
-       - "{{ zone_prefix_reverse_24 }}"
-       - "{{ zone_prefix_reverse_16 }}"
-       - "{{ zone_prefix_reverse_8 }}"
-       - "{{ zone_ipv6_reverse }}"
-       - "{{ zone_ipv6_reverse_workaround }}"
-       - "{{ testzone }}"
-       - "{{ safezone }}"
+  - name: Ensure DNS testing zones are absent.
+    ipadnszone:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      name: "{{ item }}"
+      state: absent
+    with_items:
+      - "{{ zone_prefix_reverse }}"
+      - "{{ zone_prefix_reverse_24 }}"
+      - "{{ zone_prefix_reverse_16 }}"
+      - "{{ zone_prefix_reverse_8 }}"
+      - "{{ zone_ipv6_reverse }}"
+      - "{{ zone_ipv6_reverse_workaround }}"
+      - "{{ testzone }}"
+      - "{{ safezone }}"
diff --git a/tests/dnsrecord/env_setup.yml b/tests/dnsrecord/env_setup.yml
index ebdb7570fd08081b977c8ea1da3fa1fb901e1304..0c2cfdfffcefd7dadb5624106ad1725d42f4db96 100644
--- a/tests/dnsrecord/env_setup.yml
+++ b/tests/dnsrecord/env_setup.yml
@@ -10,6 +10,7 @@
   - name: Ensure DNS testing zones are present.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ item }}"
       skip_nameserver_check: yes
       skip_overlap_check: yes
@@ -25,6 +26,7 @@
   - name: Ensure DNSSEC zone '"{{ safezone }}"' is present.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ safezone }}"
       dnssec: yes
       skip_nameserver_check: yes
diff --git a/tests/dnsrecord/test_dnsrecord.yml b/tests/dnsrecord/test_dnsrecord.yml
index 5b5d48a9cb45e5cf0d8e5f2b57dcb15e6bbe5177..176e5492516834a16718e1c0e9a78d2d6ed66b60 100644
--- a/tests/dnsrecord/test_dnsrecord.yml
+++ b/tests/dnsrecord/test_dnsrecord.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test dnsrecord
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: yes
   gather_facts: yes
 
@@ -23,6 +23,7 @@
   - name: Ensure that dns record 'host01' is present
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: host01
       zone_name: "{{ testzone }}"
       record_type: AAAA
@@ -33,6 +34,7 @@
   - name: Ensure that dns record 'host01' is present, again
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: host01
       zone_name: "{{ testzone }}"
       record_type: AAAA
@@ -43,6 +45,7 @@
   - name: Ensure that dns record 'host02' is present
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: host02
       zone_name: "{{ testzone }}"
       record_type: A
@@ -53,6 +56,7 @@
   - name: Ensure that dns record 'host02' is present, again
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: host02
       zone_name: "{{ testzone }}"
       record_type: A
@@ -63,6 +67,7 @@
   - name: Modify record 'host02' with multiple A and AAAA record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       records:
         - name: host02
           zone_name: "{{ testzone }}"
@@ -80,6 +85,7 @@
   - name: Modify record 'host02' with multiple A and AAAA record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       records:
         - name: host02
           zone_name: "{{ testzone }}"
@@ -97,6 +103,7 @@
   - name: Ensure 'host02' A6 record is present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host02
       a6_data: ::1
@@ -106,6 +113,7 @@
   - name: Ensure 'host02' A6 record is present, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host02
       a6_rec: ::1
@@ -115,6 +123,7 @@
   - name: Ensure 'host02' A6 record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host02
       a6_rec: ::1
@@ -125,6 +134,7 @@
   - name: Ensure 'host02' A6 record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host02
       a6_rec: ::1
@@ -135,6 +145,7 @@
   - name: Ensure that dns record 'host03' is present, with reverse record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: host03
       zone_name: "{{ testzone }}"
       a_ip_address: "{{ ipv4_prefix }}.103"
@@ -145,6 +156,7 @@
   - name: Ensure that dns record 'host03' is present, with reverse record, again
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: host03
       zone_name: "{{ testzone }}"
       record_type: A
@@ -156,6 +168,7 @@
   - name: Delete all entries associated with host03
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host03
       del_all: yes
@@ -166,6 +179,7 @@
   - name: Delete all entries associated with host03, again
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host03
       del_all: yes
@@ -176,6 +190,7 @@
   - name: Ensure that 'host04' has CNAME
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       record_type: CNAME
@@ -186,6 +201,7 @@
   - name: Ensure that 'host04' has CNAME, again
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       cname_hostname: "host04.{{ testzone }}"
@@ -195,6 +211,7 @@
   - name: Ensure that 'host04' CNAME is absent
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       cname_rec: "host04.{{ testzone }}"
@@ -205,6 +222,7 @@
   - name: Ensure that 'host04' CNAME is absent, again
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       record_type: CNAME
@@ -216,6 +234,7 @@
   - name: Ensure that 'host04' and 'host03' have CNAME, with cname_hostname
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       records:
         - name: host04
@@ -228,6 +247,7 @@
   - name: Ensure that 'host04' has CNAME, with cname_hostname, again
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       cname_hostname: "host04.{{ testzone }}"
@@ -237,6 +257,7 @@
   - name: Ensure that 'host04' CNAME is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       cname_rec: "host04.{{ testzone }}"
@@ -247,6 +268,7 @@
   - name: Ensure that 'host04' has A record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ip_address: "{{ ipv4_prefix }}.104"
@@ -256,6 +278,7 @@
   - name: Ensure that 'host04' has A record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ip_address: "{{ ipv4_prefix }}.104"
@@ -265,6 +288,7 @@
   - name: Ensure that 'host04' has the same A record with reverse.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       a_rec: "{{ ipv4_prefix }}.104"
@@ -275,6 +299,7 @@
   - name: Ensure that 'host04' has the same A record with reverse, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       a_rec: "{{ ipv4_prefix }}.104"
@@ -285,6 +310,7 @@
   - name: Ensure that 'host04' has another A record with reverse.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ip_address: "{{ ipv4_prefix }}.114"
@@ -294,6 +320,7 @@
   - name: Ensure that 'host04' has another A record with reverse, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ip_address: "{{ ipv4_prefix }}.114"
@@ -304,6 +331,7 @@
   - name: Ensure that 'host04' has AAAA record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       aaaa_ip_address: fd00::0004
@@ -314,6 +342,7 @@
   - name: Ensure that 'host04' has AAAA record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ip_address: fd00::0004
@@ -324,6 +353,7 @@
   - name: Ensure that 'host04' has AAAA record, without reverse.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ip_address: fd00::0014
@@ -333,6 +363,7 @@
   - name: Ensure that 'host04' previous AAAA record, now has a reverse record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       aaaa_rec: fd00::0014
@@ -343,6 +374,7 @@
   - name: Ensure that 'host04' previous AAAA record, now has a reverse record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       aaaa_rec: fd00::0014
@@ -353,6 +385,7 @@
   - name: Ensure that 'host04' has PTR record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ zone_prefix_reverse_24 }}"
       name: "124"
       ptr_hostname: "host04.{{ testzone }}"
@@ -362,6 +395,7 @@
   - name: Ensure that 'host04' has PTR record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ zone_prefix_reverse_24 }}"
       name: "124"
       ptr_hostname: "host04.{{ testzone }}"
@@ -371,6 +405,7 @@
   - name: Ensure that 'host04' has PTR record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ zone_prefix_reverse_24 }}"
       name: "124"
       ptr_rec: "host04.{{ testzone }}"
@@ -381,6 +416,7 @@
   - name: Ensure that 'host04' has PTR record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ zone_prefix_reverse_24 }}"
       name: "124"
       ptr_rec: "host04.{{ testzone }}"
@@ -391,6 +427,7 @@
   - name: Ensure that 'host04' has DNAME record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dname_target: "ipa.{{ testzone }}"
@@ -400,6 +437,7 @@
   - name: Ensure that 'host04' has DNAME record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dname_target: "ipa.{{ testzone }}"
@@ -409,6 +447,7 @@
   - name: Ensure that 'host04' DNAME record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dname_rec: "ipa.{{ testzone }}"
@@ -419,6 +458,7 @@
   - name: Ensure that 'host04' DNAME record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dname_rec: "ipa.{{ testzone }}"
@@ -431,6 +471,7 @@
   - name: Ensure that 'host04' has a A record with reverse, for NS record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ip_address: "{{ ipv4_prefix }}.114"
@@ -441,6 +482,7 @@
   - name: Ensure that 'host04' has NS record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ns_hostname: host04
@@ -450,6 +492,7 @@
   - name: Ensure that 'host04' has NS record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ns_hostname: host04
@@ -461,6 +504,7 @@
   - name: Ensure that 'host04' NS record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ns_rec: host04
@@ -471,6 +515,7 @@
   - name: Ensure that 'host04' NS record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       ns_rec: host04
@@ -481,6 +526,7 @@
   - name: Ensure that 'host04' DLV record is present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dlv_key_tag: 12345
@@ -494,6 +540,7 @@
   - name: Ensure that 'host04' DLV record is present, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dlv_key_tag: 12345
@@ -506,6 +553,7 @@
   - name: Ensure that 'host04' DLV record is present, with a different key tag.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dlv_key_tag: 4321
@@ -516,6 +564,7 @@
   - name: Ensure that 'host04' DLV second record is present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dlv_key_tag: 4321
@@ -529,6 +578,7 @@
   - name: Ensure that 'host04' DLV record is changed, in presence of multiple records.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dlv_key_tag: 54321
@@ -539,6 +589,7 @@
   - name: Ensure that 'host04' DLV record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dlv_record: 54321 3 1 08ff468cb25ccd21642989294cc33570da5eb2ba
@@ -549,6 +600,7 @@
   - name: Ensure that 'host04' DLV record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dlv_record: 54321 3 1 08ff468cb25ccd21642989294cc33570da5eb2ba
@@ -559,6 +611,7 @@
   - name: Ensure that 'host04' DLV record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       dlv_record: 4321 2 2 da39a3ee5e6b4b0d3255bfef95601890afd80709
@@ -569,6 +622,7 @@
   - name: Ensure that dns record 'iron01' is present
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: iron01
       zone_name: "{{ safezone }}"
       ip_address: "{{ ansible_facts['default_ipv4'].address }}"
@@ -578,6 +632,7 @@
   - name: Ensure that NS record for "{{ safezone }}" is present
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: iron01
       zone_name: "{{ safezone }}"
       ns_hostname: iron01
@@ -587,6 +642,7 @@
   - name: Ensure that 'iron01' DS record is present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ safezone }}"
       name: iron01
       ds_key_tag: 12345
@@ -600,6 +656,7 @@
   - name: Ensure that 'iron01' DS record is present, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ safezone }}"
       name: iron01
       ds_key_tag: 12345
@@ -612,6 +669,7 @@
   - name: Ensure that 'iron01' DS record is present, with a different key tag.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ safezone }}"
       name: iron01
       ds_key_tag: 54321
@@ -622,6 +680,7 @@
   - name: Ensure that 'iron01' DS record is present, with a different key tag, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ safezone }}"
       name: iron01
       ds_key_tag: 54321
@@ -632,6 +691,7 @@
   - name: Ensure that 'iron01' DS record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ safezone }}"
       name: iron01
       ds_rec: 54321 3 1 84763786e4213cca9a6938dba5dacd64f87ec216
@@ -642,6 +702,7 @@
   - name: Ensure that 'iron01' DS record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ safezone }}"
       name: iron01
       ds_rec: 54321 3 1 84763786e4213cca9a6938dba5dacd64f87ec216
@@ -652,6 +713,7 @@
   - name: Ensure that 'host04' AFSDB record is present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       afsdb_subtype: 1
@@ -662,6 +724,7 @@
   - name: Ensure that 'host04' AFSDB record is present, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       afsdb_subtype: 1
@@ -672,6 +735,7 @@
   - name: Ensure that 'host04' AFSDB record subtype is 2.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       afsdb_subtype: 2
@@ -682,6 +746,7 @@
   - name: Ensure that 'host04' AFSDB record subtype is 2, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       afsdb_subtype: 2
@@ -692,6 +757,7 @@
   - name: Ensure that 'host04' AFSDB record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       afsdb_rec: "2 host04.{{ testzone }}"
@@ -702,6 +768,7 @@
   - name: Ensure that 'host04' AFSDB record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       afsdb_rec: "2 host04.{{ testzone }}"
@@ -712,6 +779,7 @@
   - name: Ensure that 'host04' CERT record is present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       cert_type: 1
@@ -724,6 +792,7 @@
   - name: Ensure that 'host04' CERT record is present, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       cert_type: 1
@@ -736,6 +805,7 @@
   - name: Ensure that 'host04' CERT record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       cert_rec: "1 1234 3 {{ lookup('file', 'cert1.b64') }}"
@@ -746,6 +816,7 @@
   - name: Ensure that 'host04' CERT record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       cert_rec: 1 1234 3 "{{ lookup('file', 'cert1.b64') }}"
@@ -756,6 +827,7 @@
   - name: Ensure that 'host04' KX record is present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       kx_preference: 10
@@ -766,6 +838,7 @@
   - name: Ensure that 'host04' KX record is present, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       kx_preference: 10
@@ -776,6 +849,7 @@
   - name: Ensure that 'host04' KX record is present with preference set to 20.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       kx_preference: 20
@@ -786,6 +860,7 @@
   - name: Ensure that 'host04' KX record is present with preference set to 20, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       kx_preference: 20
@@ -796,6 +871,7 @@
   - name: Ensure that 'host04' KX record is present with preference set to 20, one more time.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       kx_preference: 20
@@ -806,6 +882,7 @@
   - name: Ensure that 'host04' KX record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       kx_rec: "20 keyex.{{ testzone }}"
@@ -816,6 +893,7 @@
   - name: Ensure that 'host04' KX record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       kx_rec: "20 keyex.{{ testzone }}"
@@ -826,6 +904,7 @@
   - name: Ensure that 'host04' MX record is present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       mx_preference: 10
@@ -836,6 +915,7 @@
   - name: Ensure that 'host04' MX record is present, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       mx_preference: 10
@@ -846,6 +926,7 @@
   - name: Ensure that 'host04' MX record is present with preference set to 20.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       mx_preference: 20
@@ -856,6 +937,7 @@
   - name: Ensure that 'host04' MX record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       mx_rec: "20 mail.{{ testzone }}"
@@ -866,6 +948,7 @@
   - name: Ensure that 'host04' MX record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       mx_rec: "20 mail.{{ testzone }}"
@@ -876,6 +959,7 @@
   - name: Ensure that 'host04' LOC record is present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       loc_lat_deg: 52
@@ -896,6 +980,7 @@
   - name: Ensure that 'host04' LOC record is present, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       loc_lat_deg: 52
@@ -916,6 +1001,7 @@
   - name: Ensure that 'host04' LOC record is present, with loc_size 1.00.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       loc_size: 1.00
@@ -926,6 +1012,7 @@
   - name: Ensure that 'host04' LOC record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       loc_rec: 52 22 23.000 N 4 53 32.000 E -2.00 1.00 10000.00 10.00
@@ -936,6 +1023,7 @@
   - name: Ensure that 'host04' LOC record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       loc_rec: 52 22 23.000 N 4 53 32.000 E -2.00 1.00 10000.00 10.00
@@ -946,6 +1034,7 @@
   - name: Ensure that '_sip._udp' service has NAPTR record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       naptr_order: 100
@@ -960,6 +1049,7 @@
   - name: Ensure that '_sip._udp' service has NAPTR record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       naptr_order: 100
@@ -974,6 +1064,7 @@
   - name: Change '_sip._udp' service NAPTR record `preference` to 20.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       naptr_preference: 20
@@ -984,6 +1075,7 @@
   - name: Ensure that '_sip._udp' service has NAPTR record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       naptr_order: 101
@@ -996,6 +1088,7 @@
   - name: Ensure that '_sip._udp' service has NAPTR record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       naptr_order: 102
@@ -1008,6 +1101,7 @@
   - name: Change '_sip._udp' service NAPTR record `preference` to 50, when multiple records are present. (BZ 1881436)
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       naptr_preference: 50
@@ -1018,6 +1112,7 @@
   - name: Ensure that '_sip._udp' service has NAPTR record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       record_type: NAPTR
@@ -1029,6 +1124,7 @@
   - name: Ensure that '_sip._udp' service has NAPTR record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       record_type: NAPTR
@@ -1040,6 +1136,7 @@
   - name: Clear NAPTR records.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       del_all: yes
@@ -1049,6 +1146,7 @@
   - name: Ensure that '_sip._udp' service has SRV record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       srv_priority: 10
@@ -1061,6 +1159,7 @@
   - name: Ensure that '_sip._udp' service has SRV record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       srv_priority: 10
@@ -1073,6 +1172,7 @@
   - name: Ensure '_sip._udp' SRV record has priority equals to 4.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       srv_priority: 4
@@ -1086,6 +1186,7 @@
   - name: Ensure '_sip._udp' SRV record has priority equals to 4, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       srv_priority: 4
@@ -1099,6 +1200,7 @@
   - name: Ensurer '_sip._udp' SRV record has priority 2, weight 20
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       srv_priority: 2
@@ -1111,6 +1213,7 @@
   - name: Ensurer '_sip._udp' SRV record has priority 2, weight 20, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       srv_priority: 2
@@ -1123,6 +1226,7 @@
   - name: Ensure that '_sip._udp' SRV record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       srv_record: "2 20 5060 sip-server.{{ testzone }}"
@@ -1133,6 +1237,7 @@
   - name: Ensure that '_sip._udp' SRV record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _sip._udp
       srv_record: "2 20 5060 sip-server.{{ testzone }}"
@@ -1144,6 +1249,7 @@
   - name: Ensure that 'host04' has SSHFP record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       sshfp_algorithm: 1
@@ -1155,6 +1261,7 @@
   - name: Ensure that 'host04' has SSHFP record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       sshfp_algorithm: 1
@@ -1166,6 +1273,7 @@
   - name: Ensure that 'host04' SSHFP record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       sshfp_rec: 1 1 d21802c61733e055b8d16296cbce300efb8a167a
@@ -1176,6 +1284,7 @@
   - name: Ensure that 'host04' SSHFP record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       sshfp_rec: 1 1 d21802c61733e055b8d16296cbce300efb8a167a
@@ -1188,6 +1297,7 @@
   - name: Ensure that 'host04' has TLSA record present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       tlsa_cert_usage: 3
@@ -1200,6 +1310,7 @@
   - name: Ensure that 'host04' has TLSA record present, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       tlsa_cert_usage: 3
@@ -1212,6 +1323,7 @@
   - name: Modify 'host04' has TLSA record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       tlsa_matching_type: 0
@@ -1222,6 +1334,7 @@
   - name: Modify 'host04' has TLSA record, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       tlsa_matching_type: 0
@@ -1232,6 +1345,7 @@
   - name: Ensure that 'host04' TLSA record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       tlsa_rec: 3 1 0 9c0ad776dbeae8d9d55b0ad42899d30235c114d5f918fd69746e4279e47bdaa2
@@ -1242,6 +1356,7 @@
   - name: Ensure that 'host04' TLSA record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       tlsa_rec: 3 1 0 9c0ad776dbeae8d9d55b0ad42899d30235c114d5f918fd69746e4279e47bdaa2
@@ -1252,6 +1367,7 @@
   - name: Ensure that 'host04' has TXT record present.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       txt_data: Some Text
@@ -1261,6 +1377,7 @@
   # - name: Ensure that 'host04' has TXT record present, again.
   #   ipadnsrecord:
   #     ipaadmin_password: SomeADMINpassword
+  #     ipaapi_context: "{{ ipa_context | default(omit) }}"
   #     zone_name: "{{ testzone }}"
   #     name: host04
   #     txt_data: Some Text
@@ -1270,6 +1387,7 @@
   - name: Change value of  'host04' TXT record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       txt_data: Some new Text
@@ -1280,6 +1398,7 @@
   - name: Add a second TXT record to 'host04'.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       txt_rec: Some Other Text
@@ -1289,6 +1408,7 @@
   - name: Add a second TXT record to 'host04', again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       txt_rec: Some Other Text
@@ -1298,6 +1418,7 @@
   - name: Ensure that one of 'host04' TXT record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       txt_rec: Some new Text
@@ -1308,6 +1429,7 @@
   - name: Ensure that one of 'host04' TXT record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       txt_rec: Some new Text
@@ -1318,6 +1440,7 @@
   - name: Ensure that 'host04' TXT record are all absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       txt_rec:
@@ -1330,6 +1453,7 @@
   - name: Ensure that 'host04' TXT record are all absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: host04
       txt_rec:
@@ -1342,6 +1466,7 @@
   - name: Ensure that '_ftp._tcp' has URI record.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _ftp._tcp
       uri_priority: 10
@@ -1353,6 +1478,7 @@
   - name: Ensure that '_ftp._tcp' has URI record, again
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _ftp._tcp
       uri_priority: 10
@@ -1364,6 +1490,7 @@
   - name: Change '_ftp._tcp' URI record weight to 3 and priority to 5.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _ftp._tcp
       uri_priority: 5
@@ -1375,6 +1502,7 @@
   - name: Verify if modification worked.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _ftp._tcp
       uri_rec: 10 1 ftp://ftp.host04.{{ testzone }}/public
@@ -1386,6 +1514,7 @@
   - name: Change '_ftp._tcp' URI record weight to 3 and priority to 5, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _ftp._tcp
       uri_priority: 5
@@ -1397,6 +1526,7 @@
   - name: Ensure that '_ftp._tcp' URI record is absent.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _ftp._tcp
       uri_rec: 5 3 "ftp://ftp.host04.{{ testzone }}/public"
@@ -1407,6 +1537,7 @@
   - name: Ensure that '_ftp._tcp' URI record is absent, again.
     ipadnsrecord:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       zone_name: "{{ testzone }}"
       name: _ftp._tcp
       uri_rec: 5 3 "ftp://ftp.host04.{{ testzone }}/public"
diff --git a/tests/dnsrecord/test_dnsrecord_client_context.yml b/tests/dnsrecord/test_dnsrecord_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..46378492d332adcd81701b0d41e8aed640c5b601
--- /dev/null
+++ b/tests/dnsrecord/test_dnsrecord_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test dnsrecord
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipadnsrecord:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test dnsrecord using client context, in client host.
+  import_playbook: test_dnsrecord.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test dnsrecord using client context, in server host.
+  import_playbook: test_dnsrecord.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/dnszone/env_cleanup.yml b/tests/dnszone/env_cleanup.yml
index 76996092accf2323bbaf0ac742d2a9e5dd013458..542247c388c341e2f89f289c62c6c2f9516b986b 100644
--- a/tests/dnszone/env_cleanup.yml
+++ b/tests/dnszone/env_cleanup.yml
@@ -2,6 +2,7 @@
 - name: Ensure zone is absent.
   ipadnszone:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
       - testzone.local
       - test1.testzone.local
diff --git a/tests/dnszone/test_dnszone.yml b/tests/dnszone/test_dnszone.yml
index 39f9cf38296c90d780feaa540232a870ae5668a6..b7e01d4c669cc8d4b3ca0abbab40a00f8541a4fb 100644
--- a/tests/dnszone/test_dnszone.yml
+++ b/tests/dnszone/test_dnszone.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test dnszone
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: true
 
@@ -14,6 +14,7 @@
   - name: Check if zone is present, when in shouldn't be.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       state: present
     check_mode: yes
@@ -23,6 +24,7 @@
   - name: Check if zone is present again, when in shouldn't be.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       state: present
     check_mode: yes
@@ -32,6 +34,7 @@
   - name: Ensure zone is present.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       state: present
     register: result
@@ -40,6 +43,7 @@
   - name: Check if zone is present, when in should be.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       state: present
     check_mode: yes
@@ -49,6 +53,7 @@
   - name: Ensure zone is present, again.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       state: present
     register: result
@@ -57,6 +62,7 @@
   - name: Ensure zone is disabled.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       state: disabled
     register: result
@@ -65,6 +71,7 @@
   - name: Ensure zone is disabled, again.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       state: disabled
     register: result
@@ -73,6 +80,7 @@
   - name: Ensure zone is enabled.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       state: enabled
     register: result
@@ -81,6 +89,7 @@
   - name: Ensure zone is enabled, again.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       state: enabled
     register: result
@@ -89,6 +98,7 @@
   - name: Ensure forward_policy is none.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       forward_policy: none
     register: result
@@ -97,6 +107,7 @@
   - name: Ensure forward_policy is none, again.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       forward_policy: none
     register: result
@@ -105,6 +116,7 @@
   - name: Ensure forward_policy is first.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       forward_policy: first
     register: result
@@ -113,6 +125,7 @@
   - name: Ensure forward_policy is first, again.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       forward_policy: first
     register: result
@@ -121,6 +134,7 @@
   - name: Ensure first forwarder is set.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       forwarders:
         - ip_address: 8.8.8.8
@@ -131,6 +145,7 @@
   - name: Ensure first and second forwarder are set.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       forwarders:
         - ip_address: 8.8.8.8
@@ -142,6 +157,7 @@
   - name: Ensure first and second forwarder are set, again.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       forwarders:
         - ip_address: 8.8.8.8
@@ -153,6 +169,7 @@
   - name: Ensure only second forwarder is set.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       forwarders:
         - ip_address: 2001:4860:4860::8888
@@ -162,6 +179,7 @@
   - name: Nothing changes.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
     register: result
     failed_when: result.changed or result.failed
@@ -169,6 +187,7 @@
   - name: Ensure no forwarders are set.
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testzone.local
       forwarders: []
     register: result
@@ -177,6 +196,7 @@
   - name: Create zones test1
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: test1.testzone.local
     register: result
     failed_when: not result.changed or result.failed
@@ -184,6 +204,7 @@
   - name: Create zones test1, again
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: test1.testzone.local
     register: result
     failed_when: result.changed or result.failed
@@ -191,6 +212,7 @@
   - name: Create zones test2
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: test2.testzone.local
     register: result
     failed_when: not result.changed or result.failed
@@ -198,6 +220,7 @@
   - name: Create zones test2, again
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: test2.testzone.local
     register: result
     failed_when: result.changed or result.failed
@@ -205,6 +228,7 @@
   - name: Create zones test3
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: test3.testzone.local
     register: result
     failed_when: not result.changed or result.failed
@@ -212,6 +236,7 @@
   - name: Create zones test3, again
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: test3.testzone.local
     register: result
     failed_when: result.changed or result.failed
@@ -219,6 +244,7 @@
   - name: Ensure multiple zones are absent
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
         - test1.testzone.local
         - test2.testzone.local
@@ -230,6 +256,7 @@
   - name: Ensure multiple zones are absent, again
     ipadnszone:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
         - test1.testzone.local
         - test2.testzone.local
diff --git a/tests/dnszone/test_dnszone_client_context.yml b/tests/dnszone/test_dnszone_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0f7f959ad36f8f2417f954b39a82ff6b40d9f4bc
--- /dev/null
+++ b/tests/dnszone/test_dnszone_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test dnszone
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipadnszone:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test dnszone using client context, in client host.
+  import_playbook: test_dnszone.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test dnszone using client context, in server host.
+  import_playbook: test_dnszone.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/env_freeipa_facts.yml b/tests/env_freeipa_facts.yml
index 6622634dce88a69e23110d47bdfa5454ff6b0fcc..a150ecd64557f69ffd399df852a6898c3ac8b6e4 100644
--- a/tests/env_freeipa_facts.yml
+++ b/tests/env_freeipa_facts.yml
@@ -12,8 +12,20 @@
     cmd: 'ipa --version | sed -n "s/VERSION: \([^,]*\).*API_VERSION: \([^,]*\).*/\1\\n\2/p"'
   register: ipa_cmd_version
 
+- name: Verify if host is an IPA server or client.
+  shell:
+    cmd: |
+      echo SomeADMINpassword | kinit -c {{ KRB5CCNAME }} admin
+      RESULT=$(KRB5CCNAME={{ KRB5CCNAME }} ipa server-show `hostname` && echo SERVER || echo CLIENT)
+      kdestroy -A -c {{ KRB5CCNAME }}
+      echo $RESULT
+  vars:
+    KRB5CCNAME: "__check_ipa_host_is_client_or_server__"
+  register: output
+
 - name: Set FreeIPA facts.
   set_fact:
     ipa_version: "{{ ipa_cmd_version.stdout_lines[0] }}"
     ipa_api_version: "{{ ipa_cmd_version.stdout_lines[1] }}"
+    ipa_host_is_client: "{{ (output.stdout_lines[-1] == 'CLIENT') | bool }}"
     trust_test_is_supported: no
diff --git a/tests/group/test_group.yml b/tests/group/test_group.yml
index 855443bd4d64c369f83c3a8b9293ddda03a36d7c..74b170c5bcbefcd159a5ade037c2cce9aa9b53b7 100644
--- a/tests/group/test_group.yml
+++ b/tests/group/test_group.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test group
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -8,18 +8,21 @@
   - name: Ensure users user1, user2 and user3 are absent
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: user1,user2,user3
       state: absent
 
   - name: Ensure group group3, group2 and group1 are absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group3,group2,group1
       state: absent
 
   - name: Ensure users user1..user3 are present
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       users:
       - name: user1
         first: user1
@@ -36,6 +39,7 @@
   - name: Ensure group1 is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group1
     register: result
     failed_when: not result.changed or result.failed
@@ -43,6 +47,7 @@
   - name: Ensure group1 is present again
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group1
     register: result
     failed_when: result.changed or result.failed
@@ -50,6 +55,7 @@
   - name: Ensure group2 is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group2
     register: result
     failed_when: not result.changed or result.failed
@@ -57,6 +63,7 @@
   - name: Ensure group2 is present again
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group2
     register: result
     failed_when: result.changed or result.failed
@@ -64,6 +71,7 @@
   - name: Ensure group3 is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group3
     register: result
     failed_when: not result.changed or result.failed
@@ -71,6 +79,7 @@
   - name: Ensure group3 is present again
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group3
     register: result
     failed_when: result.changed or result.failed
@@ -78,6 +87,7 @@
   - name: Ensure groups group2 and group3 are present in group group1
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group1
       group:
       - group2
@@ -89,6 +99,7 @@
   - name: Ensure groups group2 and group3 are present in group group1 again
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group1
       group:
       - group2
@@ -100,6 +111,7 @@
   - name: Ensure group3 ia present in group group1
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group1
       group:
       - group3
@@ -110,6 +122,7 @@
   - name: Ensure users user1, user2 and user3 are present in group group1
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group1
       user:
       - user1
@@ -122,6 +135,7 @@
   - name: Ensure users user1, user2 and user3 are present in group group1 again
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group1
       user:
       - user1
@@ -133,6 +147,7 @@
 
   #- ipagroup:
   #    ipaadmin_password: SomeADMINpassword
+  #    ipaapi_context: "{{ ipa_context | default(omit) }}"
   #    name: group1
   #    user:
   #    - user7
@@ -141,6 +156,7 @@
   - name: Ensure user user7 is absent in group group1
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group1
       user:
       - user7
@@ -152,6 +168,7 @@
   - name: Ensure group group4 is absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group4
       state: absent
     register: result
@@ -160,6 +177,7 @@
   - name: Ensure group group3, group2 and group1 are absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group3,group2,group1
       state: absent
     register: result
@@ -168,8 +186,8 @@
   - name: Ensure users user1, user2 and user3 are absent
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: user1,user2,user3
       state: absent
     register: result
     failed_when: not result.changed or result.failed
-
diff --git a/tests/group/test_group_client_context.yml b/tests/group/test_group_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5e58cbfdcefe70194d98f69e125c63bb3c62e979
--- /dev/null
+++ b/tests/group/test_group_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test group
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test group using client context, in client host.
+  import_playbook: test_group.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test group using client context, in server host.
+  import_playbook: test_group.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/hbacrule/test_hbacrule.yml b/tests/hbacrule/test_hbacrule.yml
index e93a74dcff52aab78b9ced2d92f3b530a5c70a74..0c1616444fcb60316260741bbf91a4a25fabd555 100644
--- a/tests/hbacrule/test_hbacrule.yml
+++ b/tests/hbacrule/test_hbacrule.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test hbacrule
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
 
   tasks:
@@ -14,6 +14,7 @@
   - name: Ensure test hosts are absent
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - "{{ 'testhost01.' + ipaserver_domain }}"
       - "{{ 'testhost02.' + ipaserver_domain }}"
@@ -24,30 +25,35 @@
   - name: Ensure test hostgroups are absent
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup01,testhostgroup02,testhostgroup03,testhostgroup04
       state: absent
 
   - name: Ensure test users are absent
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testuser01,testuser02,testuser03,testuser04
       state: absent
 
   - name: Ensure test user groups are absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup01,testgroup02,testgroup03,testgroup04
       state: absent
 
   - name: Ensure test HBAC Services are absent
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvc01,testhbacsvc02,testhbacsvc03,testhbacsvc04
       state: absent
 
   - name: Ensure test HBAC Service Groups are absent
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvcgroup01,testhbacsvcgroup02,testhbacsvcgroup03,testhbacsvcgroup04
       state: absent
 
@@ -56,6 +62,7 @@
   - name: Ensure hosts "{{ 'host[1..4].' + ipaserver_domain }}" are present
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       hosts:
       - name: "{{ 'testhost01.' + ipaserver_domain }}"
         force: yes
@@ -71,6 +78,7 @@
   - name: Ensure host-group testhostgroup01 is present
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup01
     register: result
     failed_when: not result.changed or result.failed
@@ -78,6 +86,7 @@
   - name: Ensure host-group testhostgroup02 is present
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup02
     register: result
     failed_when: not result.changed or result.failed
@@ -85,6 +94,7 @@
   - name: Ensure host-group testhostgroup03 is present
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup03
     register: result
     failed_when: not result.changed or result.failed
@@ -92,6 +102,7 @@
   - name: Ensure host-group testhostgroup04 is present
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup04
     register: result
     failed_when: not result.changed or result.failed
@@ -99,6 +110,7 @@
   - name: Ensure testusers are present
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       users:
       - name: testuser01
         first: test
@@ -118,6 +130,7 @@
   - name: Ensure user group testgroup01 is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup01
     register: result
     failed_when: not result.changed or result.failed
@@ -125,6 +138,7 @@
   - name: Ensure user group testgroup02 is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup02
     register: result
     failed_when: not result.changed or result.failed
@@ -132,6 +146,7 @@
   - name: Ensure user group testgroup03 is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup03
     register: result
     failed_when: not result.changed or result.failed
@@ -139,6 +154,7 @@
   - name: Ensure user group testgroup04 is present
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup04
     register: result
     failed_when: not result.changed or result.failed
@@ -146,6 +162,7 @@
   - name: Ensure HBAC Service testhbacsvc01 is present
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvc01
     register: result
     failed_when: not result.changed or result.failed
@@ -153,6 +170,7 @@
   - name: Ensure HBAC Service testhbacsvc02 is present
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvc02
     register: result
     failed_when: not result.changed or result.failed
@@ -160,6 +178,7 @@
   - name: Ensure HBAC Service testhbacsvc03 is present
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvc03
     register: result
     failed_when: not result.changed or result.failed
@@ -167,6 +186,7 @@
   - name: Ensure HBAC Service testhbacsvc04 is present
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvc04
     register: result
     failed_when: not result.changed or result.failed
@@ -174,6 +194,7 @@
   - name: Ensure HBAC Service Group testhbacsvcgroup01 is present
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvcgroup01
     register: result
     failed_when: not result.changed or result.failed
@@ -181,6 +202,7 @@
   - name: Ensure HBAC Service Group testhbacsvcgroup02 is present
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvcgroup02
     register: result
     failed_when: not result.changed or result.failed
@@ -188,6 +210,7 @@
   - name: Ensure HBAC Service Group testhbacsvcgroup03 is present
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvcgroup03
     register: result
     failed_when: not result.changed or result.failed
@@ -195,6 +218,7 @@
   - name: Ensure HBAC Service Group testhbacsvcgroup04 is present
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvcgroup04
     register: result
     failed_when: not result.changed or result.failed
@@ -202,6 +226,7 @@
   - name: Ensure test HBAC rule hbacrule01 is absent
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       state: absent
 
@@ -210,6 +235,7 @@
   - name: Ensure HBAC rule hbacrule01 is present
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
     register: result
     failed_when: not result.changed or result.failed
@@ -217,6 +243,7 @@
   - name: Ensure HBAC rule hbacrule01 is present again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
     register: result
     failed_when: result.changed or result.failed
@@ -226,6 +253,7 @@
   - name: Ensure HBAC rule hbacrule01 is present with hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost01.' + ipaserver_domain }}"
@@ -241,6 +269,7 @@
   - name: Ensure HBAC rule hbacrule01 is present with hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost01.' + ipaserver_domain }}"
@@ -258,6 +287,7 @@
   - name: Ensure test HBAC rule hbacrule01 host members are absent
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost01.' + ipaserver_domain }}"
@@ -270,6 +300,7 @@
   - name: Ensure test HBAC rule hbacrule01 host members are absent again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost01.' + ipaserver_domain }}"
@@ -282,6 +313,7 @@
   - name: Ensure test HBAC rule hbacrule01 hostgroup members are absent
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hostgroup: testhostgroup01,testhostgroup02
       state: absent
@@ -292,6 +324,7 @@
   - name: Ensure test HBAC rule hbacrule01 hostgroup members are absent again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hostgroup: testhostgroup01,testhostgroup02
       state: absent
@@ -302,6 +335,7 @@
   - name: Ensure test HBAC rule hbacrule01 user members are absent
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       user: testuser01,testuser02
       state: absent
@@ -312,6 +346,7 @@
   - name: Ensure test HBAC rule hbacrule01 user members are absent again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       user: testuser01,testuser02
       state: absent
@@ -322,6 +357,7 @@
   - name: Ensure test HBAC rule hbacrule01 user group members are absent
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       group: testgroup01,testgroup02
       state: absent
@@ -332,6 +368,7 @@
   - name: Ensure test HBAC rule hbacrule01 user group members are absent again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       group: testgroup01,testgroup02
       state: absent
@@ -342,6 +379,7 @@
   - name: Ensure test HBAC rule hbacrule01 hbacsvc members are absent
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hbacsvc: testhbacsvc01,testhbacsvc02
       state: absent
@@ -352,6 +390,7 @@
   - name: Ensure test HBAC rule hbacrule01 hbacsvc members are absent again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hbacsvc: testhbacsvc01,testhbacsvc02
       state: absent
@@ -362,6 +401,7 @@
   - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are absent
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
       state: absent
@@ -372,6 +412,7 @@
   - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are absent again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
       state: absent
@@ -384,6 +425,7 @@
   - name: Ensure test HBAC rule hbacrule01 host members are present
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost01.' + ipaserver_domain }}"
@@ -395,6 +437,7 @@
   - name: Ensure test HBAC rule hbacrule01 host members are present again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost01.' + ipaserver_domain }}"
@@ -406,6 +449,7 @@
   - name: Ensure test HBAC rule hbacrule01 hostgroup members are present
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hostgroup: testhostgroup01,testhostgroup02
       action: member
@@ -415,6 +459,7 @@
   - name: Ensure test HBAC rule hbacrule01 hostgroup members are present again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hostgroup: testhostgroup01,testhostgroup02
       action: member
@@ -424,6 +469,7 @@
   - name: Ensure test HBAC rule hbacrule01 user members are present
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       user: testuser01,testuser02
       action: member
@@ -433,6 +479,7 @@
   - name: Ensure test HBAC rule hbacrule01 user members are present again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       user: testuser01,testuser02
       action: member
@@ -442,6 +489,7 @@
   - name: Ensure test HBAC rule hbacrule01 user group members are present
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       group: testgroup01,testgroup02
       action: member
@@ -451,6 +499,7 @@
   - name: Ensure test HBAC rule hbacrule01 user group members are present again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       group: testgroup01,testgroup02
       action: member
@@ -460,6 +509,7 @@
   - name: Ensure test HBAC rule hbacrule01 hbacsvc members are present
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hbacsvc: testhbacsvc01,testhbacsvc02
       action: member
@@ -469,6 +519,7 @@
   - name: Ensure test HBAC rule hbacrule01 hbacsvc members are present again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hbacsvc: testhbacsvc01,testhbacsvc02
       action: member
@@ -478,6 +529,7 @@
   - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are present
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
       action: member
@@ -487,6 +539,7 @@
   - name: Ensure test HBAC rule hbacrule01 hbacsvcgroup members are present again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       hbacsvcgroup: testhbacsvcgroup01,testhbacsvcgroup02
       action: member
@@ -498,6 +551,7 @@
   - name: Ensure HBAC rule hbacrule01 is present with different hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost03.' + ipaserver_domain }}"
@@ -513,6 +567,7 @@
   - name: Ensure HBAC rule hbacrule01 is present with different hosts, hostgroups, users, groups, hbassvcs and hbacsvcgroups again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost03.' + ipaserver_domain }}"
@@ -530,6 +585,7 @@
   - name: Ensure HBAC rule hbacrule01 members (same) are present
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost01.' + ipaserver_domain }}"
@@ -549,6 +605,7 @@
   - name: Ensure HBAC rule hbacrule01 members are absent
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost03.' + ipaserver_domain }}"
@@ -566,6 +623,7 @@
   - name: Ensure HBAC rule hbacrule01 members are absent again
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "{{ 'testhost03.' + ipaserver_domain }}"
@@ -585,6 +643,7 @@
   - name: Ensure HBAC rule hbacrule01 simple host members are usable
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "testhost01"
@@ -595,6 +654,7 @@
   - name: Ensure HBAC rule hbacrule01 simple host members are usable again (and match)
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       host:
       - "testhost01"
@@ -607,12 +667,14 @@
   - name: Ensure test HBAC rule hbacrule01 is absent
     ipahbacrule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: hbacrule01
       state: absent
 
   - name: Ensure test hosts are absent
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - "{{ 'testhost01.' + ipaserver_domain }}"
       - "{{ 'testhost02.' + ipaserver_domain }}"
@@ -623,29 +685,34 @@
   - name: Ensure test hostgroups are absent
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhostgroup01,testhostgroup02,testhostgroup03,testhostgroup04
       state: absent
 
   - name: Ensure test users are absent
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testuser01,testuser02,testuser03,testuser04
       state: absent
 
   - name: Ensure test user groups are absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testgroup01,testgroup02,testgroup03,testgroup04
       state: absent
 
   - name: Ensure test HBAC Services are absent
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvc01,testhbacsvc02,testhbacsvc03,testhbacsvc04
       state: absent
 
   - name: Ensure test HBAC Service Groups are absent
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testhbacsvcgroup01,testhbacsvcgroup02,testhbacsvcgroup03,testhbacsvcgroup04
       state: absent
diff --git a/tests/hbacrule/test_hbacrule_client_context.yml b/tests/hbacrule/test_hbacrule_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..efa3fa9c0e928ce5767edb54b7450a7b70d437b3
--- /dev/null
+++ b/tests/hbacrule/test_hbacrule_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test hbacrule
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipahbacrule:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test hbacrule using client context, in client host.
+  import_playbook: test_hbacrule.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test hbacrule using client context, in server host.
+  import_playbook: test_hbacrule.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/hbacsvc/test_hbacsvc.yml b/tests/hbacsvc/test_hbacsvc.yml
index c91cf39b0fba8b0e6952ccf024bf9b13dc9c89e6..135fd11fa9edcbd87065c07e703ff65dd1e7e9cd 100644
--- a/tests/hbacsvc/test_hbacsvc.yml
+++ b/tests/hbacsvc/test_hbacsvc.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test hbacsvc
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -8,12 +8,14 @@
   - name: Ensure HBAC Service for http is absent
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: http,tftp
       state: absent
 
   - name: Ensure HBAC Service for http is present
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: http
     register: result
     failed_when: not result.changed or result.failed
@@ -21,6 +23,7 @@
   - name: Ensure HBAC Service for http is present again
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: http
     register: result
     failed_when: result.changed or result.failed
@@ -28,6 +31,7 @@
   - name: Ensure HBAC Service for tftp is present
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: tftp
       description: TFTP service
     register: result
@@ -36,6 +40,7 @@
   - name: Ensure HBAC Service for tftp is present again
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: tftp
       description: TFTP service
     register: result
@@ -44,6 +49,7 @@
   - name: Ensure HBAC Services for http and tftp are absent
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: http,tftp
       state: absent
     register: result
@@ -52,6 +58,7 @@
   - name: Ensure HBAC Services for http and tftp are absent again
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: http,tftp
       state: absent
     register: result
diff --git a/tests/hbacsvc/test_hbacsvc_client_context.yml b/tests/hbacsvc/test_hbacsvc_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..161fa0221ec6b31b760d0a9a2036a19967c4ab7c
--- /dev/null
+++ b/tests/hbacsvc/test_hbacsvc_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test hbacsvc
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipahbacsvc:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test hbacsvc using client context, in client host.
+  import_playbook: test_hbacsvc.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test hbacsvc using client context, in server host.
+  import_playbook: test_hbacsvc.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/hbacsvcgroup/test_hbacsvcgroup.yml b/tests/hbacsvcgroup/test_hbacsvcgroup.yml
index d0cd02cd49a59c19a73ad0beab58bf273ab317bb..024b39043220153ff27a8518bbf2314175f81fb9 100644
--- a/tests/hbacsvcgroup/test_hbacsvcgroup.yml
+++ b/tests/hbacsvcgroup/test_hbacsvcgroup.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test hbacsvcgroup
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -8,17 +8,20 @@
   - name: Ensure HBAC Service Group login is absent
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
       state: absent
 
   - name: Ensure HBAC Service for sshd is present
     ipahbacsvc:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
 
   - name: Ensure HBAC Service Group login is present
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
     register: result
     failed_when: not result.changed or result.failed
@@ -26,6 +29,7 @@
   - name: Ensure HBAC Service Group login is present again
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
     register: result
     failed_when: result.changed or result.failed
@@ -33,6 +37,7 @@
   - name: Ensure HBAC Service sshd is present in HBAC Service Group login
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
       hbacsvc:
       - sshd
@@ -43,6 +48,7 @@
   - name: Ensure HBAC Service sshd is present in HBAC Service Group login again
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
       hbacsvc:
       - sshd
@@ -53,6 +59,7 @@
   - name: Ensure HBAC Services sshd and foo are absent in HBAC Service Group login
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
       hbacsvc:
       - sshd
@@ -65,6 +72,7 @@
   - name: Ensure HBAC Services sshd and foo are absent in HBAC Service Group login again
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
       hbacsvc:
       - sshd
@@ -77,6 +85,7 @@
   - name: Ensure HBAC Service Group login is absent
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
       state: absent
     register: result
@@ -85,6 +94,7 @@
   - name: Ensure HBAC Service Group login is absent again
     ipahbacsvcgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: login
       state: absent
     register: result
diff --git a/tests/hbacsvcgroup/test_hbacsvcgroup_client_context.yml b/tests/hbacsvcgroup/test_hbacsvcgroup_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..569fe5b46f526f1cf1de38542ad2b36a2748279a
--- /dev/null
+++ b/tests/hbacsvcgroup/test_hbacsvcgroup_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test hbacsvcgroup
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipahbacsvcgroup:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test hbacsvcgroup using client context, in client host.
+  import_playbook: test_hbacsvcgroup.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test hbacsvcgroup using client context, in server host.
+  import_playbook: test_hbacsvcgroup.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/host/test_host.yml b/tests/host/test_host.yml
index 3b186525a2c86d34db2e0be28bfb7f6421074227..bee85ef74948d859bbe1ae60cb048b2a8a40a9b1 100644
--- a/tests/host/test_host.yml
+++ b/tests/host/test_host.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test host
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
 
   tasks:
@@ -21,6 +21,7 @@
   - name: Host absent
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - "{{ host1_fqdn }}"
       - "{{ host2_fqdn }}"
@@ -39,6 +40,7 @@
   - name: Host "{{ host1_fqdn }}" present
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host1_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.201' }}"
       update_dns: yes
@@ -49,6 +51,7 @@
   - name: Host "{{ host1_fqdn }}" present again
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host1_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.201' }}"
       update_dns: yes
@@ -59,6 +62,7 @@
   - name: Host "{{ host2_fqdn }}" present
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host2_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.202' }}"
       update_dns: yes
@@ -69,6 +73,7 @@
   - name: Host "{{ host2_fqdn }}" present again
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host2_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.202' }}"
       update_dns: yes
@@ -79,6 +84,7 @@
   - name: Host "{{ host3_fqdn }}" present
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host3_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.203' }}"
       update_dns: yes
@@ -89,6 +95,7 @@
   - name: Host "{{ host3_fqdn }}" present again
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host3_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.203' }}"
       update_dns: yes
@@ -99,6 +106,7 @@
   - name: Host "{{ host4_fqdn }}" present
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host4_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.204' }}"
       update_dns: yes
@@ -109,6 +117,7 @@
   - name: Host "{{ host4_fqdn }}" present again
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host4_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.204' }}"
       update_dns: yes
@@ -119,6 +128,7 @@
   - name: Host "{{ host5_fqdn }}" present
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host5_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.205' }}"
       update_dns: yes
@@ -129,6 +139,7 @@
   - name: Host "{{ host5_fqdn }}" present again
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host5_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.205' }}"
       update_dns: yes
@@ -139,6 +150,7 @@
   - name: Host "{{ host6_fqdn }}" present
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host6_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.206' }}"
       update_dns: yes
@@ -149,6 +161,7 @@
   - name: Host "{{ host6_fqdn }}" present again
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ host6_fqdn }}"
       ip_address: "{{ ipv4_prefix + '.206' }}"
       update_dns: yes
@@ -161,6 +174,7 @@
   #- name: Hosts host1..host6 disabled
   #  ipahost:
   #    ipaadmin_password: SomeADMINpassword
+  #    ipaapi_context: "{{ ipa_context | default(omit) }}"
   #    name:
   #    - "{{ host1_fqdn }}"
   #    - "{{ host2_fqdn }}"
@@ -175,6 +189,7 @@
   #- name: Hosts host1..host6 disabled again
   #  ipahost:
   #    ipaadmin_password: SomeADMINpassword
+  #    ipaapi_context: "{{ ipa_context | default(omit) }}"
   #    name:
   #    - "{{ host1_fqdn }}"
   #    - "{{ host2_fqdn }}"
@@ -189,6 +204,7 @@
   - name: Hosts host1..host6 absent
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - "{{ host1_fqdn }}"
       - "{{ host2_fqdn }}"
@@ -204,6 +220,7 @@
   - name: Hosts host1..host6 absent again
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - "{{ host1_fqdn }}"
       - "{{ host2_fqdn }}"
@@ -215,4 +232,3 @@
       state: absent
     register: result
     failed_when: result.changed or result.failed
-
diff --git a/tests/host/test_host_client_context.yml b/tests/host/test_host_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ba3074015896101ac9b7a41148b31f11bffbd397
--- /dev/null
+++ b/tests/host/test_host_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test host
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test automember using client context, in client host.
+  import_playbook: test_host.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test automember using client context, in server host.
+  import_playbook: test_host.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/hostgroup/test_hostgroup.yml b/tests/hostgroup/test_hostgroup.yml
index 1b1ba7a626b7c28b36cd94b3959e1798ee19ae3f..076f76d1bf1cb027e53b7a29134760792efb0b96 100644
--- a/tests/hostgroup/test_hostgroup.yml
+++ b/tests/hostgroup/test_hostgroup.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test hostgroup
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: true
 
@@ -13,6 +13,7 @@
   - name: Ensure host-group databases, mysql-server and oracle-server are absent
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - databases
       - mysql-server
@@ -22,6 +23,7 @@
   - name: Test hosts db1 and db2 absent
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - "{{ 'db1.' + ipaserver_domain }}"
       - "{{ 'db2.' + ipaserver_domain }}"
@@ -30,6 +32,7 @@
   - name: Host "{{ 'db1.' + ipaserver_domain }}" present
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ 'db1.' + ipaserver_domain }}"
       force: yes
     register: result
@@ -38,6 +41,7 @@
   - name: Host "{{ 'db2.' + ipaserver_domain }}" present
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ 'db2.' + ipaserver_domain }}"
       force: yes
     register: result
@@ -46,6 +50,7 @@
   - name: Ensure host-group mysql-server is present
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: mysql-server
       state: present
     register: result
@@ -54,6 +59,7 @@
   - name: Ensure host-group mysql-server is present again
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: mysql-server
       state: present
     register: result
@@ -62,6 +68,7 @@
   - name: Ensure host-group oracle-server is present
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: oracle-server
       state: present
     register: result
@@ -70,6 +77,7 @@
   - name: Ensure host-group oracle-server is present again
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: oracle-server
       state: present
     register: result
@@ -78,6 +86,7 @@
   - name: Ensure host-group databases is present
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: databases
       state: present
       host:
@@ -90,6 +99,7 @@
   - name: Ensure host-group databases is present again
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: databases
       state: present
       host:
@@ -102,6 +112,7 @@
   - name: Ensure host db2 is member of host-group databases
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: databases
       state: present
       host:
@@ -113,6 +124,7 @@
   - name: Ensure host db2 is member of host-group databases again
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: databases
       state: present
       host:
@@ -124,6 +136,7 @@
   - name: Ensure host-group mysql-server is member of host-group databases
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: databases
       state: present
       hostgroup:
@@ -135,6 +148,7 @@
   - name: Ensure host-group mysql-server is member of host-group databases again
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: databases
       state: present
       hostgroup:
@@ -146,6 +160,7 @@
   - name: Ensure host-group oracle-server is member of host-group databases (again)
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: databases
       state: present
       hostgroup:
@@ -157,6 +172,7 @@
   - name: Ensure host-group databases, mysql-server and oracle-server are absent
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - databases
       - mysql-server
@@ -168,6 +184,7 @@
   - name: Ensure host-group databases, mysql-server and oracle-server are absent again
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - databases
       - mysql-server
@@ -179,6 +196,7 @@
   - name: Test hosts db1 and db2 absent
     ipahost:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - "{{ 'db1.' + ipaserver_domain }}"
       - "{{ 'db2.' + ipaserver_domain }}"
diff --git a/tests/hostgroup/test_hostgroup_client_context.yml b/tests/hostgroup/test_hostgroup_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a234da902f4b64dd75970a1d3bdc2c8a81a60067
--- /dev/null
+++ b/tests/hostgroup/test_hostgroup_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test hostgroup
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipahostgroup:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test hostgroup using client context, in client host.
+  import_playbook: test_hostgroup.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test hostgroup using client context, in server host.
+  import_playbook: test_hostgroup.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/location/test_location.yml b/tests/location/test_location.yml
index 10aed32fda68072b754e2226ad1d04584d593bd5..3cda4115db7fd5c0d857565fc069cdbb18ba6e84 100644
--- a/tests/location/test_location.yml
+++ b/tests/location/test_location.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test location
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
 
   tasks:
@@ -10,6 +10,7 @@
   - name: Ensure location my_location1 is absent
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: my_location1
       state: absent
 
@@ -20,6 +21,7 @@
   - name: Ensure location my_location1 is present
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: my_location1
     register: result
     failed_when: not result.changed or result.failed
@@ -27,6 +29,7 @@
   - name: Ensure location my_location1 is present again
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: my_location1
     register: result
     failed_when: result.changed or result.failed
@@ -34,6 +37,7 @@
   - name: Ensure location my_location1 is present with description
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: my_location1
       description: My Location 1
     register: result
@@ -42,6 +46,7 @@
   - name: Ensure location my_location1 is present again with description
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: my_location1
       description: My Location 1
     register: result
@@ -50,6 +55,7 @@
   - name: Ensure location my_location1 is absent
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: my_location1
       state: absent
     register: result
@@ -58,6 +64,7 @@
   - name: Ensure location my_location1 is absent again
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: my_location1
       state: absent
     register: result
@@ -68,5 +75,6 @@
   - name: Ensure location my_location1 is absent
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: my_location1
       state: absent
diff --git a/tests/location/test_location_client_context.yml b/tests/location/test_location_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..00d5749f8938a3949573b8082265b72c086c7083
--- /dev/null
+++ b/tests/location/test_location_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test location
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipalocation:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test location using client context, in client host.
+  import_playbook: test_location.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test location using client context, in server host.
+  import_playbook: test_location.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/permission/test_permission.yml b/tests/permission/test_permission.yml
index b4c04fcdf2e47ba2ff42dab3e015caaff4453e46..d7edc102c0def2e0c99d69ac4a95e022b067e128 100644
--- a/tests/permission/test_permission.yml
+++ b/tests/permission/test_permission.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test permission
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
 
   tasks:
@@ -9,6 +9,7 @@
   - name: Ensure testing groups are present.
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ item }}"
       state: present
     with_items:
@@ -20,6 +21,7 @@
   - name: Ensure permission perm-test-1 is absent
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - perm-test-1
       - perm-test-bindtype-test
@@ -31,6 +33,7 @@
   - name: Ensure permission perm-test-1 is present
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       object_type: host
       memberof: rbacgroup1
@@ -42,6 +45,7 @@
   - name: Ensure permission perm-test-1 is present again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       object_type: host
       memberof: rbacgroup1
@@ -53,6 +57,7 @@
   - name: Ensure permission perm-test-1 has an extra filter '(cn=*.internal.*)'
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       filter: '(cn=*.internal.*)'
       action: member
@@ -62,6 +67,7 @@
   - name: Ensure permission perm-test-1 has an extra filter '(cn=*.internal.*)', again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       filter: '(cn=*.internal.*)'
       action: member
@@ -71,6 +77,7 @@
   - name: Ensure permission perm-test-1 `right` has `write`
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       right: write
       action: member
@@ -80,6 +87,7 @@
   - name: Ensure permission perm-test-1 `right` has `write`, again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       right: write
       action: member
@@ -89,6 +97,7 @@
   - name: Ensure permission perm-test-1 `right` has no `write`
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       right: write
       action: member
@@ -99,6 +108,7 @@
   - name: Ensure permission perm-test-1 `right` has no `write`, again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       right: write
       action: member
@@ -109,6 +119,7 @@
   - name: Ensure permission perm-test-1 `memberof` has `rbackgroup2`
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       memberof: rbacgroup2
       action: member
@@ -118,6 +129,7 @@
   - name: Ensure permission perm-test-1 `memberof` has `rbackgroup2`, again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       memberof: rbacgroup2
       action: member
@@ -127,6 +139,7 @@
   - name: Ensure permission perm-test-1 `memberof` item `rbackgroup1` is absent
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       memberof: rbacgroup1
       action: member
@@ -137,6 +150,7 @@
   - name: Ensure permission perm-test-1 `memberof` item `rbackgroup1` is absent, again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       memberof: rbacgroup1
       action: member
@@ -147,6 +161,7 @@
   - name: Ensure permission perm-test-1 is present with attr carlicense
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - carlicense
@@ -156,6 +171,7 @@
   - name: Ensure permission perm-test-1 is present with attr carlicense again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - carlicense
@@ -165,6 +181,7 @@
   - name: Ensure permission perm-test-1 is present with attr carlicense and displayname
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - carlicense
@@ -175,6 +192,7 @@
   - name: Ensure permission perm-test-1 is present with attr carlicense and displayname again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - carlicense
@@ -185,6 +203,7 @@
   - name: Ensure attr gecos is present in permission perm-test-1
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - gecos
@@ -195,6 +214,7 @@
   - name: Ensure attr gecos is present in permission perm-test-1 again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - gecos
@@ -205,6 +225,7 @@
   - name: Ensure attr gecos is absent in permission perm-test-1
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - gecos
@@ -216,6 +237,7 @@
   - name: Ensure attr gecos is absent in permission perm-test-1 again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       attrs:
       - gecos
@@ -227,6 +249,7 @@
   - name: Ensure attributes carlicense and displayname are present in permission "System{{':'}} Update DNS Entries"
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "System: Update DNS Entries"
       attrs:
       - carlicense
@@ -238,6 +261,7 @@
   - name: Ensure attributes carlicense and displayname are present in permission "System{{':'}} Update DNS Entries" again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "System: Update DNS Entries"
       attrs:
       - carlicense
@@ -249,6 +273,7 @@
   - name: Ensure attributes carlicense and displayname are present in permission "System{{':'}} Update DNS Entries"
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "System: Update DNS Entries"
       attrs:
       - carlicense
@@ -261,6 +286,7 @@
   - name: Ensure attributes carlicense and displayname are present in permission "System{{':'}} Update DNS Entries" again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "System: Update DNS Entries"
       attrs:
       - carlicense
@@ -273,6 +299,7 @@
   - name: Ensure permission perm-test-1 has rawfilter '(objectclass=ipagroup)'
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       rawfilter: '(objectclass=ipagroup)'
       action: member
@@ -282,6 +309,7 @@
   - name: Ensure permission perm-test-1 has rawfilter '(objectclass=ipagroup)', again
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       rawfilter: '(objectclass=ipagroup)'
       action: member
@@ -291,6 +319,7 @@
   - name: Ensure filter and rawfilter cannot be used together.
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       rawfilter: '(objectclass=ipagroup)'
       filter: '(cn=*.internal.*)'
@@ -301,6 +330,7 @@
   - name: Rename permission perm-test-1 to perm-test-renamed
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       rename: perm-test-renamed
       state: renamed
@@ -310,6 +340,7 @@
   - name: Ensure permission perm-test-1 is absent
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-1
       state: absent
     register: result
@@ -318,6 +349,7 @@
   - name: Ensure permission perm-test-renamed is present
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-renamed
       object_type: host
       right: all
@@ -327,6 +359,7 @@
   - name: Ensure permission with bindtype 'self' is present, if IPA version >= 4.8.7
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-bindtype-test
       bindtype: self
       object_type: host
@@ -338,6 +371,7 @@
   - name: Fail to set permission perm-test-renamed bindtype to 'self', if IPA version < 4.8.7
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: perm-test-bindtype-test
       bindtype: self
       object_type: host
@@ -351,6 +385,7 @@
   - name: Ensure testing permissions are absent
     ipapermission:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - perm-test-1
       - perm-test-bindtype-test
@@ -360,6 +395,7 @@
   - name: Ensure testing groups are absent.
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ item }}"
       state: absent
     with_items:
diff --git a/tests/permission/test_permission_client_context.yml b/tests/permission/test_permission_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b351c08651f2edbcdb4ed707d2c8b67d610229c9
--- /dev/null
+++ b/tests/permission/test_permission_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test permission
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test permission using client context, in client host.
+  import_playbook: test_permission.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test permission using client context, in server host.
+  import_playbook: test_permission.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/privilege/test_privilege.yml b/tests/privilege/test_privilege.yml
index 0f6a29d7e7a9a4d8e102346cf02cc4d91bc55b29..71c08d1617187789ff83b7a871dfc90c57ec63fd 100644
--- a/tests/privilege/test_privilege.yml
+++ b/tests/privilege/test_privilege.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test privilege
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
 
   tasks:
@@ -10,6 +10,7 @@
   - name: Ensure privilege "Broad Privilege" is absent
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - Broad Privilege
       - DNS Privilege
@@ -22,6 +23,7 @@
   - name: Ensure privilege Broad Privilege is present
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       description: Broad Privilege
     register: result
@@ -30,6 +32,7 @@
   - name: Ensure privilege Broad Privilege is present again
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       description: Broad Privilege
     register: result
@@ -38,6 +41,7 @@
   - name: Change privilege Broad Privilege description
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       description: Broad Privilege description
     register: result
@@ -46,6 +50,7 @@
   - name: Ensure privilege Broad Privilege has permissions
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       permission:
       - "Write IPA Configuration"
@@ -58,6 +63,7 @@
   - name: Ensure privilege Broad Privilege has permissions, again
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       permission:
       - "Write IPA Configuration"
@@ -70,6 +76,7 @@
   - name: Ensure privilege Broad Privilege member permission "Write IPA Configuration" is absent
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       permission:
       - "Write IPA Configuration"
@@ -81,6 +88,7 @@
   - name: Ensure privilege Broad Privilege member permission "Write IPA Configuration" is absent again
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       permission:
       - "Write IPA Configuration"
@@ -92,6 +100,7 @@
   - name: Ensure privilege Broad Privilege is absent
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       state: absent
     register: result
@@ -100,6 +109,7 @@
   - name: Ensure privilege Broad Privilege is present
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
     register: result
     failed_when: not result.changed or result.failed
@@ -107,6 +117,7 @@
   - name: Ensure privilege Broad Privilege is renamed to "DNS Privilege"
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       rename: DNS Privilege
       state: renamed
@@ -116,6 +127,7 @@
   - name: Ensure privilege Broad Privilege cannot be renamed, because it does not exist.
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       rename: DNS Privilege
       state: renamed
@@ -125,6 +137,7 @@
   - name: Ensure privilege cannot be renamed to the same name.
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: DNS Privilege
       rename: DNS Privilege
       state: renamed
@@ -134,6 +147,7 @@
   - name: Ensure privilege cannot be renamed to the same name.
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: DNS Privilege
       rename: DNS Privilege
       state: renamed
@@ -143,12 +157,14 @@
   - name: Ensure "Broad Privilege" is absent.
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       state: absent
 
   - name: Ensure privilege Broad Privilege is created with permission. (issue 529)
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       permission:
       - "Write IPA Configuration"
@@ -158,6 +174,7 @@
   - name: Ensure privilege Broad Privilege is created with permission, again. (issue 529)
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: Broad Privilege
       permission:
       - "Write IPA Configuration"
@@ -169,6 +186,7 @@
   - name: Ensure privilege testing privileges are absent
     ipaprivilege:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - Broad Privilege
       - DNS Privilege
diff --git a/tests/privilege/test_privilege_client_context.yml b/tests/privilege/test_privilege_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cd125f754ed28d7f9356815afa287d2f96e0aa01
--- /dev/null
+++ b/tests/privilege/test_privilege_client_context.yml
@@ -0,0 +1,38 @@
+---
+- name: Test privilege
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipaprivilege:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test privilege using client context, in client host.
+  import_playbook: test_privilege.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test privilege using client context, in server host.
+  import_playbook: test_privilege.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
+ 
diff --git a/tests/pwpolicy/test_pwpolicy.yml b/tests/pwpolicy/test_pwpolicy.yml
index 03f08e0fbce7021a675e52b8e881abe6f100e69e..5ac18d769cf66d3f8b5a65ce60331a16b81eb495 100644
--- a/tests/pwpolicy/test_pwpolicy.yml
+++ b/tests/pwpolicy/test_pwpolicy.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test pwpolicy
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -8,23 +8,27 @@
   - name: Ensure maxlife of 90 for global_policy
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       maxlife: 90
 
   - name: Ensure absence of group ops
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: ops
       state: absent
 
   - name: Ensure absence of pwpolicies for group ops
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: ops
       state: absent
 
   - name: Ensure presence of group ops
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: ops
       state: present
     register: result
@@ -33,6 +37,7 @@
   - name: Ensure presence of pwpolicies for group ops
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: ops
       minlife: 7
       maxlife: 49
@@ -49,6 +54,7 @@
   - name: Ensure presence of pwpolicies for group ops again
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: ops
       minlife: 7
       maxlife: 49
@@ -65,6 +71,7 @@
   - name: Ensure maxlife of 49 for global_policy
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       maxlife: 49
     register: result
     failed_when: not result.changed or result.failed
@@ -72,6 +79,7 @@
   - name: Ensure maxlife of 49 for global_policy again
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       maxlife: 49
     register: result
     failed_when: result.changed or result.failed
@@ -79,6 +87,7 @@
   - name: Ensure absence of pwpoliciy global_policy will fail
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       state: absent
     register: result
     failed_when: not result.failed or "'global_policy' can not be made absent." not in result.msg
@@ -86,6 +95,7 @@
   - name: Ensure absence of pwpolicies for group ops
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: ops
       state: absent
     register: result
@@ -94,6 +104,7 @@
   - name: Ensure maxlife of 90 for global_policy
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       maxlife: 90
     register: result
     failed_when: not result.changed or result.failed
@@ -101,6 +112,7 @@
   - name: Ensure absence of pwpolicies for group ops
     ipapwpolicy:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: ops
       state: absent
     register: result
diff --git a/tests/pwpolicy/test_pwpolicy_client_context.yml b/tests/pwpolicy/test_pwpolicy_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..362f567fd82c744bfa59a546ae49771d8ae4c6a6
--- /dev/null
+++ b/tests/pwpolicy/test_pwpolicy_client_context.yml
@@ -0,0 +1,38 @@
+---
+- name: Test pwpolicy
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipapwpolicy:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test pwpolicy using client context, in client host.
+  import_playbook: test_pwpolicy.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test pwpolicy using client context, in server host.
+  import_playbook: test_pwpolicy.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
+  
diff --git a/tests/role/env_cleanup.yml b/tests/role/env_cleanup.yml
index 24064a1c7d7c2073ba19146d4ac4891768f44f88..0b459651d3a81ebc490f5382c755d3eb9d96ffbc 100644
--- a/tests/role/env_cleanup.yml
+++ b/tests/role/env_cleanup.yml
@@ -2,6 +2,7 @@
 - name: Ensure test user is absent.
   ipauser:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
     - user01
     - user02
@@ -11,6 +12,7 @@
 - name: Ensure test group is absent.
   ipagroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
     - group01
     - group02
@@ -19,6 +21,7 @@
 - name: Ensure test hostgroup is absent.
   ipahostgroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
     - hostgroup01
     - hostgroup02
@@ -27,6 +30,7 @@
 - name: Ensure test host is absent.
   ipahost:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
     - "{{ host1_fqdn }}"
     - "{{ host2_fqdn }}"
@@ -35,6 +39,7 @@
 - name: Ensure test service is absent.
   ipaservice:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
     - "service01/{{ host1_fqdn }}"
     - "service02/{{ host2_fqdn }}"
@@ -43,6 +48,7 @@
 - name: Ensure test roles are absent.
   iparole:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
     - renamerole
     - testrole
diff --git a/tests/role/env_setup.yml b/tests/role/env_setup.yml
index 32bd32a912eda2e9d86ae92d4b0caffa9b339823..eb72c82be49c03a491075353eb066c9cc279ceeb 100644
--- a/tests/role/env_setup.yml
+++ b/tests/role/env_setup.yml
@@ -5,6 +5,7 @@
 - name: Ensure test user is present.
   ipauser:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     users:
     - name: user01
       first: First
@@ -19,6 +20,7 @@
 - name: Ensure test group is present.
   ipagroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: "{{ item }}"
   with_items:
   - group01
@@ -27,6 +29,7 @@
 - name: Ensure test host is present.
   ipahost:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: "{{ item }}"
     force: yes
   with_items:
@@ -36,6 +39,7 @@
 - name: Ensure test hostgroup is present.
   ipahostgroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: "{{ item[0] }}"
     host:
       - "{{ item[1] }}"
@@ -46,6 +50,7 @@
 - name: Ensure test service is present.
   ipaservice:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: "{{ item }}"
     force: yes
   with_items:
diff --git a/tests/role/test_role.yml b/tests/role/test_role.yml
index 0c4661e669eb20fdc3bf7fc390f1c4e8822d43c4..6f9ba90d2bbf7f5c2d49f4a6977e413d70a4709b 100644
--- a/tests/role/test_role.yml
+++ b/tests/role/test_role.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test role module
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: yes
   gather_facts: yes
 
@@ -15,6 +15,7 @@
   - name: Ensure role is present.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: renamerole
       description: A role in IPA.
     register: result
@@ -23,6 +24,7 @@
   - name: Ensure role is present, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: renamerole
       description: A role in IPA.
     register: result
@@ -31,6 +33,7 @@
   - name: Rename role.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: renamerole
       rename: testrole
     register: result
@@ -41,6 +44,7 @@
   - name: Rename role, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: renamerole
       rename: testrole
     register: result
@@ -49,6 +53,7 @@
   - name: Ensure role has member has privileges.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       privilege:
       - DNS Servers
@@ -60,6 +65,7 @@
   - name: Ensure role has member has privileges, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       privilege:
       - DNS Servers
@@ -71,6 +77,7 @@
   - name: Ensure role has less privileges.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       privilege:
       - Host Administrators
@@ -82,6 +89,7 @@
   - name: Ensure role has less privileges, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       privilege:
       - Host Administrators
@@ -93,6 +101,7 @@
   - name: Ensure role has member has privileges restored.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       privilege:
       - DNS Servers
@@ -104,6 +113,7 @@
   - name: Ensure role has member has privileges restored, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       privilege:
       - DNS Servers
@@ -115,6 +125,7 @@
   - name: Ensure role member privileges are absent.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       privilege:
       - DNS Servers
@@ -127,6 +138,7 @@
   - name: Ensure role member privileges are absent, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       privilege:
       - DNS Servers
@@ -139,6 +151,7 @@
   - name: Ensure invalid privileged is not assigned to role.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       privilege: Invalid Privilege
       action: member
@@ -148,6 +161,7 @@
   - name: Ensure role has member user present.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       user:
       - user01
@@ -158,6 +172,7 @@
   - name: Ensure role has member user present, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       user:
       - user01
@@ -168,6 +183,7 @@
   - name: Ensure role has member user absent.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       user:
       - user01
@@ -179,6 +195,7 @@
   - name: Ensure role has member user absent, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       user:
       - user01
@@ -190,6 +207,7 @@
   - name: Ensure role has member group present.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       group:
       - group01
@@ -200,6 +218,7 @@
   - name: Ensure role has member group present, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       group:
       - group01
@@ -210,6 +229,7 @@
   - name: Ensure role has member group absent.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       group:
       - group01
@@ -221,6 +241,7 @@
   - name: Ensure role has member group absent, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       group:
       - group01
@@ -232,6 +253,7 @@
   - name: Ensure role has member host present.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       host:
       - "{{ host1_fqdn }}"
@@ -242,6 +264,7 @@
   - name: Ensure role has member host present, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       host:
       - "{{ host1_fqdn }}"
@@ -252,6 +275,7 @@
   - name: Ensure role has member host absent.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       host:
       - "{{ host1_fqdn }}"
@@ -263,6 +287,7 @@
   - name: Ensure role has member host absent, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       host:
       - "{{ host1_fqdn }}"
@@ -274,6 +299,7 @@
   - name: Ensure role has member hostgroup present.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       hostgroup:
       - hostgroup01
@@ -284,6 +310,7 @@
   - name: Ensure role has member hostgroup present, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       hostgroup:
       - hostgroup01
@@ -294,6 +321,7 @@
   - name: Ensure role has member hostgroup absent.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       hostgroup:
       - hostgroup01
@@ -305,6 +333,7 @@
   - name: Ensure role has member hostgroup absent, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       hostgroup:
       - hostgroup01
@@ -316,6 +345,7 @@
   - name: Ensure role is absent.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       state: absent
     register: result
@@ -324,6 +354,7 @@
   - name: Ensure role is absent, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       state: absent
     register: result
@@ -332,6 +363,7 @@
   - name: Ensure role with members is present.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       user:
       - user01
@@ -352,6 +384,7 @@
   - name: Ensure role with members is present, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       user:
       - user01
@@ -372,6 +405,7 @@
   - name: Ensure role is absent.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       state: absent
     register: result
@@ -380,6 +414,7 @@
   - name: Ensure role is absent, again.
     iparole:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrole
       state: absent
     register: result
diff --git a/tests/role/test_role_client_context.yml b/tests/role/test_role_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..594d3ec1e4af0078f1324873bdac8738066dd9d3
--- /dev/null
+++ b/tests/role/test_role_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test role
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    iparole:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test role using client context, in client host.
+  import_playbook: test_role.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test role using client context, in server host.
+  import_playbook: test_role.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/selfservice/test_selfservice.yml b/tests/selfservice/test_selfservice.yml
index 3f036acb6427710ce45f721ec1a95869a003537d..a5305187d2041e5ba9bba3e5393b20ac406f7a08 100644
--- a/tests/selfservice/test_selfservice.yml
+++ b/tests/selfservice/test_selfservice.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test selfservice
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
 
   tasks:
@@ -10,6 +10,7 @@
   - name: Ensure selfservice "Users can manage their own name details" is absent
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       state: absent
 
@@ -20,6 +21,7 @@
   - name: Ensure selfservice "Users can manage their own name details" is present
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       permission: write
       attribute:
@@ -32,6 +34,7 @@
   - name: Ensure selfservice "Users can manage their own name details" is present again
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       permission: write
       attribute:
@@ -44,6 +47,7 @@
   - name: Ensure selfservice "Users can manage their own name details" is present with different attribute initials
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       permission: write
       attribute:
@@ -54,6 +58,7 @@
   - name: Ensure selfservice "Users can manage their own name details" is present with different attribute initials again
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       permission: write
       attribute:
@@ -64,6 +69,7 @@
   - name: Ensure selfservice "Users can manage their own name details" member attributes givenname, displayname and title are present
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       attribute:
       - givenname
@@ -76,6 +82,7 @@
   - name: Ensure selfservice "Users can manage their own name details" member attributes givenname, displayname and title are present again
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       attribute:
       - givenname
@@ -88,6 +95,7 @@
   - name: Ensure selfservice "Users can manage their own name details" member attribute title is absent
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       attribute:
       - title
@@ -99,6 +107,7 @@
   - name: Ensure selfservice "Users can manage their own name details" member attribute title is absent again
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       attribute:
       - title
@@ -112,6 +121,7 @@
   - name: Ensure selfservice "Users can manage their own name details" is present with different read,write permission
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       permission: read,write
     register: result
@@ -120,6 +130,7 @@
   - name: Ensure selfservice "Users can manage their own name details" is present with different read,write permission again
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       permission: read,write
     register: result
@@ -128,6 +139,7 @@
   - name: Ensure selfservice "Users can manage their own name details" fails with bad permission read,read
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       permission: read,read
     register: result
@@ -136,6 +148,7 @@
   - name: Ensure selfservice "Users can manage their own name details" fails with bad permission read,write,write
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       permission: read,write,write
     register: result
@@ -144,6 +157,7 @@
   - name: Ensure selfservice "Users can manage their own name details" fails with bad attribute title,title
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       attribute:
       - title
@@ -156,5 +170,6 @@
   - name: Ensure selfservice "Users can manage their own name details" is absent
     ipaselfservice:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "Users can manage their own name details"
       state: absent
diff --git a/tests/selfservice/test_selfservice_client_context.yml b/tests/selfservice/test_selfservice_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d720aa537021f1c8fbe93a97101aa9438666f6f5
--- /dev/null
+++ b/tests/selfservice/test_selfservice_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test selfservice
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipaselfservice:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test selfservice using client context, in client host.
+  import_playbook: test_selfservice.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test selfservice using client context, in server host.
+  import_playbook: test_selfservice.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/server/test_server.yml b/tests/server/test_server.yml
index b7f0193aacf5464626b276c0e96be86f68970624..0f97b2ac56bd97f8b7f0c0fa2afa866a1ad6fc0a 100644
--- a/tests/server/test_server.yml
+++ b/tests/server/test_server.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test server
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: yes
 
@@ -30,18 +30,21 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" without location
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       location: ""
 
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" without service weight
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       service_weight: -1
 
   - name: Ensure location "mylocation" is absent
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: mylocation
       state: absent
 
@@ -50,6 +53,7 @@
   - name: Ensure location "mylocation" is present
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: mylocation
     register: result
     failed_when: not result.changed or result.failed
@@ -59,6 +63,7 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" is present
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
     register: result
     failed_when: result.changed or result.failed
@@ -66,6 +71,7 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" with location "mylocation"
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       location: "mylocation"
     register: result
@@ -74,6 +80,7 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" with location "mylocation" again
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       location: "mylocation"
     register: result
@@ -82,6 +89,7 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" without location
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       location: ""
     register: result
@@ -90,6 +98,7 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" without location again
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       location: ""
     register: result
@@ -98,6 +107,7 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" with service weight 1
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       service_weight: 1
     register: result
@@ -106,6 +116,7 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" with service weight 1 again
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       service_weight: 1
     register: result
@@ -114,6 +125,7 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" without service weight
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       service_weight: -1
     register: result
@@ -122,6 +134,7 @@
   - name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" without service weight again
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ ipa_server_name + '.' + ipaserver_domain }}"
       service_weight: -1
     register: result
@@ -134,6 +147,7 @@
   - name: Ensure server "{{ 'absent.' + ipaserver_domain }}" is absent
     ipaserver:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: "{{ 'absent.' + ipaserver_domain }}"
       state: absent
     register: result
@@ -148,6 +162,7 @@
   - name: Ensure location "mylocation" is absent
     ipalocation:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: mylocation
       state: absent
     register: result
diff --git a/tests/server/test_server_client_context.yml b/tests/server/test_server_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d1f310900106b95fbd8223528423ddc2537e596d
--- /dev/null
+++ b/tests/server/test_server_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test server
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipaserver:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test server using client context, in client host.
+  import_playbook: test_server.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test server using client context, in server host.
+  import_playbook: test_server.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/service/env_cleanup.yml b/tests/service/env_cleanup.yml
index 1a137bbe38d48bce60bbbf8a5a986405b0aff9af..964807d24a0b7f789e41b2ef38859cc5afc83049 100644
--- a/tests/service/env_cleanup.yml
+++ b/tests/service/env_cleanup.yml
@@ -3,6 +3,7 @@
 - name: Ensure services are absent.
   ipaservice:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
       - "HTTP/{{ svc_fqdn }}"
       - "HTTP/{{ nohost_fqdn }}"
@@ -16,6 +17,7 @@
 - name: Ensure host "{{ svc_fqdn }}" is absent
   ipahost:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: "{{ svc_fqdn }}"
     update_dns: yes
     state: absent
@@ -23,6 +25,7 @@
 - name: Ensure host is absent
   ipahost:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
       - "{{ host1_fqdn }}"
       - "{{ host2_fqdn }}"
@@ -34,6 +37,7 @@
 - name: Ensure testing users are absent.
   ipauser:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
     - user01
     - user02
@@ -42,6 +46,7 @@
 - name: Ensure testing groups are absent.
   ipagroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
     - group01
     - group02
@@ -50,6 +55,7 @@
 - name: Ensure testing hostgroup hostgroup01 is absent.
   ipagroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
       - hostgroup01
     state: absent
@@ -57,6 +63,7 @@
 - name: Ensure testing hostgroup hostgroup02 is absent.
   ipagroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name:
       - hostgroup02
     state: absent
@@ -64,6 +71,7 @@
 - name: Remove IP address for "nohost" host.
   ipadnsrecord:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     zone_name: "{{ test_domain }}."
     name: nohost
     del_all: yes
diff --git a/tests/service/env_setup.yml b/tests/service/env_setup.yml
index 8f2e2866f87e6e1f9b90071c15056e9667dedaf3..9c92a64ec15a2888eded5cb816d93474b60e5c84 100644
--- a/tests/service/env_setup.yml
+++ b/tests/service/env_setup.yml
@@ -10,6 +10,7 @@
 - name: Add IP address for "nohost" host.
   ipadnsrecord:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     zone_name: "{{ test_domain }}."
     name: nohost
     a_ip_address: "{{ ipv4_prefix + '.100' }}"
@@ -17,6 +18,7 @@
 - name: Add hosts for tests.
   ipahost:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     hosts:
       - name: "{{ host1_fqdn }}"
         ip_address: "{{ ipv4_prefix + '.101' }}"
@@ -31,6 +33,7 @@
 - name: Ensure testing user user01 is present.
   ipauser:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: user01
     first: user01
     last: last
@@ -38,6 +41,7 @@
 - name: Ensure testing user user02 is present.
   ipauser:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: user02
     first: user02
     last: last
@@ -45,19 +49,23 @@
 - name: Ensure testing group group01 is present.
   ipagroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: group01
 
 - name: Ensure testing group group02 is present.
   ipagroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: group02
 
 - name: Ensure testing hostgroup hostgroup01 is present.
   ipahostgroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: hostgroup01
 
 - name: Ensure testing hostgroup hostgroup02 is present.
   ipahostgroup:
     ipaadmin_password: SomeADMINpassword
+    ipaapi_context: "{{ ipa_context | default(omit) }}"
     name: hostgroup02
diff --git a/tests/service/test_service.yml b/tests/service/test_service.yml
index 0c1683d1d6c9e826b0cf9ed36f35c69eae3b09ab..3436f032b0dbb8c8ad6efc489bb38c4f7f5ef12c 100644
--- a/tests/service/test_service.yml
+++ b/tests/service/test_service.yml
@@ -12,7 +12,7 @@
 #
 ---
 - name: Test service
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: yes
 
   tasks:
@@ -28,6 +28,7 @@
       - name: Ensure service is present
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           pac_type:
             - MS-PAC
@@ -44,6 +45,7 @@
       - name: Ensure service is present, again
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           pac_type:
             - MS-PAC
@@ -60,6 +62,7 @@
       - name: Modify service.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           pac_type: NONE
           ok_as_delegate: yes
@@ -70,6 +73,7 @@
       - name: Modify service, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           pac_type: NONE
           ok_as_delegate: yes
@@ -80,6 +84,7 @@
       - name: Ensure service is present, without host object.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ nohost_fqdn }}"
           skip_host_check: yes
         register: result
@@ -88,6 +93,7 @@
       - name: Ensure service is present, without host object, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ nohost_fqdn }}"
           skip_host_check: yes
         register: result
@@ -96,6 +102,7 @@
       - name: Ensure service is present, with host not in DNS.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: HTTP/svc.ihavenodns.info
           skip_host_check: no
           force: yes
@@ -105,6 +112,7 @@
       - name: Ensure service is present, with host not in DNS, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: HTTP/svc.ihavenodns.info
           skip_host_check: no
           force: yes
@@ -114,6 +122,7 @@
       - name: Ensure service is present, whithout host object and with host not in DNS.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: HTTP/no.idontexist.info
           skip_host_check: yes
           force: yes
@@ -123,6 +132,7 @@
       - name: Ensure service is present, whithout host object and with host not in DNS, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: HTTP/no.idontexist.info
           skip_host_check: yes
           force: yes
@@ -132,6 +142,7 @@
       - name: Principal host/test.example.com present in service.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           principal:
             - host/test.example.com
@@ -142,6 +153,7 @@
       - name: Principal host/test.example.com present in service, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           principal:
             - host/test.example.com
@@ -153,6 +165,7 @@
       - name: Principal host/test.example.com absent in service.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           principal:
             - host/test.example.com
@@ -164,6 +177,7 @@
       - name: Principal host/test.example.com absent in service, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           principal:
             - host/test.example.com
@@ -176,6 +190,7 @@
       - name: Ensure host can manage service.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           host:
           - "{{ host1_fqdn }}"
@@ -187,6 +202,7 @@
       - name: Ensure host can manage service, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           host: "{{ host1_fqdn }}"
           action: member
@@ -196,6 +212,7 @@
       - name: Ensure host cannot manage service.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           host:
           - "{{ host1_fqdn }}"
@@ -208,6 +225,7 @@
       - name: Ensure host cannot manage service, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           host:
           - "{{ host1_fqdn }}"
@@ -220,6 +238,7 @@
       - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab present for users, groups, hosts and hostgroups.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           allow_create_keytab_user:
           - user01
@@ -240,6 +259,7 @@
       - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab present for users, groups, hosts and hostgroups, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           allow_create_keytab_user:
           - user01
@@ -260,6 +280,7 @@
       - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab absent for users, groups, hosts and hostgroups.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           allow_create_keytab_user:
           - user01
@@ -281,6 +302,7 @@
       - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab absent for users, groups, hosts and hostgroups, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           allow_create_keytab_user:
           - user01
@@ -302,6 +324,7 @@
       - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab present for users, groups, hosts and hostgroups
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           allow_retrieve_keytab_user:
           - user01
@@ -322,6 +345,7 @@
       - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab present for users, groups, hosts and hostgroups, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           allow_retrieve_keytab_user:
           - user01
@@ -342,6 +366,7 @@
       - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab absent for users, groups, hosts and hostgroups.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           allow_retrieve_keytab_user:
           - user01
@@ -363,6 +388,7 @@
       - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab absent for users, groups, hosts and hostgroups, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           allow_retrieve_keytab_user:
           - user01
@@ -384,6 +410,7 @@
       - name: Ensure service is absent
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           continue: yes
           state: absent
@@ -393,6 +420,7 @@
       - name: Ensure service is absent, again
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           continue: yes
           state: absent
@@ -402,6 +430,7 @@
       - name: Ensure service is present, with multiple auth_ind values.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           auth_ind: otp,radius
           skip_host_check: no
@@ -412,6 +441,7 @@
       - name: Ensure service is present, with multiple auth_ind values, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           auth_ind: otp,radius
           skip_host_check: no
@@ -422,6 +452,7 @@
       - name: Clear auth_ind.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           auth_ind: ""
           skip_host_check: no
@@ -432,6 +463,7 @@
       - name: Clear auth_ind, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "HTTP/{{ svc_fqdn }}"
           auth_ind: ""
           skip_host_check: no
@@ -442,6 +474,7 @@
       - name: Ensure services are absent.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name:
           - "HTTP/{{ svc_fqdn }}"
           - "HTTP/{{ nohost_fqdn }}"
@@ -455,6 +488,7 @@
       - name: Ensure services are absent.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name:
           - "HTTP/{{ svc_fqdn }}"
           - "HTTP/{{ nohost_fqdn }}"
@@ -468,6 +502,7 @@
       - name: Ensure SMB service is present.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "{{ host1_fqdn }}"
           pac_type: NONE
           smb: yes
@@ -478,6 +513,7 @@
       - name: Ensure SMB service is again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "{{ host1_fqdn }}"
           pac_type: NONE
           smb: yes
@@ -488,6 +524,7 @@
       - name: Modify SMB service.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "{{ host1_fqdn }}"
           smb: yes
           netbiosname: SAMBASVC
@@ -503,6 +540,7 @@
       - name: Modify SMB service, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "{{ host1_fqdn }}"
           smb: yes
           netbiosname: SAMBASVC
@@ -518,6 +556,7 @@
       - name: Ensure SMB service is absent.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "cifs/{{ host1_fqdn }}"
           continue: yes
           state: absent
@@ -527,6 +566,7 @@
       - name: Ensure SMB service is absent, again.
         ipaservice:
           ipaadmin_password: SomeADMINpassword
+          ipaapi_context: "{{ ipa_context | default(omit) }}"
           name: "cifs/{{ host1_fqdn }}"
           continue: yes
           state: absent
diff --git a/tests/service/test_service_client_context.yml b/tests/service/test_service_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9b27dc482eb2bb8382ed1956cbd7d122fcea6881
--- /dev/null
+++ b/tests/service/test_service_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test service
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test service using client context, in client host.
+  import_playbook: test_service.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test service using client context, in server host.
+  import_playbook: test_service.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/sudocmd/test_sudocmd.yml b/tests/sudocmd/test_sudocmd.yml
index 992dba7d936728cb080d408bb1459f95e92cf44d..f41cd991a19494fe74daf03b500a3dfd68b0e5e8 100644
--- a/tests/sudocmd/test_sudocmd.yml
+++ b/tests/sudocmd/test_sudocmd.yml
@@ -1,7 +1,7 @@
 ---
 
 - name: Test sudocmd
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -9,6 +9,7 @@
   - name: Ensure sudocmds are absent
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/bin/su
       - /usr/sbin/ifconfig
@@ -18,6 +19,7 @@
   - name: Ensure sudocmd is present
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: /usr/bin/su
       state: present
     register: result
@@ -26,6 +28,7 @@
   - name: Ensure sudocmd is present again
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: /usr/bin/su
       state: present
     register: result
@@ -34,6 +37,7 @@
   - name: Ensure sudocmd is absent
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: /usr/bin/su
       state: absent
     register: result
@@ -42,6 +46,7 @@
   - name: Ensure sudocmd is absent again
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: /usr/bin/su
       state: absent
     register: result
@@ -50,6 +55,7 @@
   - name: Ensure multiple sudocmd are present
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/sbin/ifconfig
       - /usr/sbin/iwlist
@@ -60,6 +66,7 @@
   - name: Ensure multiple sudocmd are present again
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/sbin/ifconfig
       - /usr/sbin/iwlist
@@ -70,6 +77,7 @@
   - name: Ensure multiple sudocmd are absent
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/sbin/ifconfig
       - /usr/sbin/iwlist
@@ -80,6 +88,7 @@
   - name: Ensure multiple sudocmd are absent again
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/sbin/ifconfig
       - /usr/sbin/iwlist
@@ -90,6 +99,7 @@
   - name: Ensure sudocmds are absent
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/bin/su
       - /usr/sbin/ifconfig
@@ -99,6 +109,7 @@
   - name: Ensure sudocmds are absent
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/sbin/ifconfig
       state: absent
@@ -106,6 +117,7 @@
   - name: Ensure sudocmds are present
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/sbin/iwlist
       state: present
@@ -113,6 +125,7 @@
   - name: Ensure multiple sudocmd are absent when only one was present
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/sbin/ifconfig
       - /usr/sbin/iwlist
diff --git a/tests/sudocmd/test_sudocmd_client_context.yml b/tests/sudocmd/test_sudocmd_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..48f517565d84701df58f7d134099815571afa99d
--- /dev/null
+++ b/tests/sudocmd/test_sudocmd_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test sudocmd
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipasudocmd:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test sudocmd using client context, in client host.
+  import_playbook: test_sudocmd.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test sudocmd using client context, in server host.
+  import_playbook: test_sudocmd.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/sudocmdgroup/test_sudocmdgroup.yml b/tests/sudocmdgroup/test_sudocmdgroup.yml
index 17c6088569ada896648a05c3603d909e9c335223..59f6306167e07621a38f017b3ab558d4947e5147 100644
--- a/tests/sudocmdgroup/test_sudocmdgroup.yml
+++ b/tests/sudocmdgroup/test_sudocmdgroup.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test sudocmdgroup
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -8,6 +8,7 @@
   - name: Ensure sudocmds are present
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /usr/bin/su
       - /usr/sbin/ifconfig
@@ -17,12 +18,14 @@
   - name: Ensure sudocmdgroup is absent
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       state: absent
 
   - name: Ensure sudocmdgroup is present
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       state: present
     register: result
@@ -31,6 +34,7 @@
   - name: Ensure sudocmdgroup is present again
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       state: present
     register: result
@@ -39,6 +43,7 @@
   - name: Ensure sudocmdgroup is absent
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       state: absent
     register: result
@@ -47,6 +52,7 @@
   - name: Ensure sudocmdgroup is absent again
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       state: absent
     register: result
@@ -55,6 +61,7 @@
   - name: Ensure sudocmdgroup is present, with sudocmds.
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/ifconfig
@@ -66,6 +73,7 @@
   - name: Ensure sudocmdgroup is present, with sudocmds, again.
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/ifconfig
@@ -85,6 +93,7 @@
   - name: Ensure sudocmdgroup, with sudocmds, is absent
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       state: absent
     register: result
@@ -93,6 +102,7 @@
   - name: Ensure sudocmdgroup, with sudocmds, is absent again
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       state: absent
     register: result
@@ -101,6 +111,7 @@
   - name: Ensure testing sudocmdgroup is present
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       state: present
     register: result
@@ -109,6 +120,7 @@
   - name: Ensure sudo commands are present in existing sudocmdgroup
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/ifconfig
@@ -120,6 +132,7 @@
   - name: Ensure sudo commands are present in existing sudocmdgroup, again
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/ifconfig
@@ -131,6 +144,7 @@
   - name: Ensure sudo commands are absent in existing sudocmdgroup
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/ifconfig
@@ -143,6 +157,7 @@
   - name: Ensure sudo commands are absent in existing sudocmdgroup, again
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/ifconfig
@@ -155,6 +170,7 @@
   - name: Ensure sudo commands are present in sudocmdgroup
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/ifconfig
@@ -167,6 +183,7 @@
   - name: Ensure one sudo command is not present in sudocmdgroup
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/ifconfig
@@ -178,6 +195,7 @@
   - name: Ensure one sudo command is present in sudocmdgroup
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/ifconfig
@@ -189,6 +207,7 @@
   - name: Ensure the other sudo command is not present in sudocmdgroup
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/iwlist
@@ -200,6 +219,7 @@
   - name: Ensure the other sudo commandsis not present in sudocmdgroup, again
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: network
       sudocmd:
       - /usr/sbin/iwlist
diff --git a/tests/sudocmdgroup/test_sudocmdgroup_client_context.yml b/tests/sudocmdgroup/test_sudocmdgroup_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c5a3ae678ea7ff018d8acd7261fd95c428f42291
--- /dev/null
+++ b/tests/sudocmdgroup/test_sudocmdgroup_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test sudocmdgroup
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipasudocmdgroup:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test sudocmdgroup using client context, in client host.
+  import_playbook: test_sudocmdgroup.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test sudocmdgroup using client context, in server host.
+  import_playbook: test_sudocmdgroup.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/sudorule/test_sudorule.yml b/tests/sudorule/test_sudorule.yml
index 3b01a085bc770e7492ee15185306e502e43abc4b..dc37e77ad705fd0a023f2bcfd43db2672e2ec084 100644
--- a/tests/sudorule/test_sudorule.yml
+++ b/tests/sudorule/test_sudorule.yml
@@ -1,7 +1,7 @@
 ---
 
 - name: Test sudorule
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: true
 
@@ -11,18 +11,21 @@
   - name: Ensure user is absent
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: user01
       state: absent
 
   - name: Ensure group is absent
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group01
       state: absent
 
   - name: Ensure user is present
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: user01
       first: user
       last: zeroone
@@ -30,24 +33,28 @@
   - name: Ensure group is present, with user01 on it.
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: group01
       user: user01
 
   - name: Ensure sudocmdgroup is absent
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: test_sudorule
       state: absent
 
   - name: Ensure hostgroup is present, with a host.
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: cluster
       host: "{{ ansible_facts['fqdn'] }}"
 
   - name: Ensure some sudocmds are available
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
           - /sbin/ifconfig
           - /usr/bin/vim
@@ -56,6 +63,7 @@
   - name: Ensure sudocmdgroup is available
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: test_sudorule
       sudocmd: /usr/bin/vim
       state: present
@@ -63,6 +71,7 @@
   - name: Ensure sudorules are absent
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - testrule1
       - allusers
@@ -75,6 +84,7 @@
   - name: Ensure sudorule is present
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
     register: result
     failed_when: not result.changed or result.failed
@@ -82,6 +92,7 @@
   - name: Ensure sudorule is present again
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
     register: result
     failed_when: result.changed or result.failed
@@ -89,6 +100,7 @@
   - name: Ensure user01 is on the list of users sudorule execute as.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       runasuser:
         - user01
@@ -99,6 +111,7 @@
   - name: Ensure user01 is on the list of users sudorule execute as, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       runasuser:
         - user01
@@ -109,6 +122,7 @@
   - name: Ensure user01 is not on the list of users sudorule execute as.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       runasuser:
         - user01
@@ -120,6 +134,7 @@
   - name: Ensure user01 is not on the list of users sudorule execute as, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       runasuser:
         - user01
@@ -131,6 +146,7 @@
   - name: Ensure group01 is on the list of group sudorule execute as.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       runasgroup:
         - group01
@@ -141,6 +157,7 @@
   - name: Ensure group01 is on the list of group sudorule execute as, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       runasgroup:
         - group01
@@ -151,6 +168,7 @@
   - name: Ensure group01 is not on the list of group sudorule execute as.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       runasgroup:
         - group01
@@ -162,6 +180,7 @@
   - name: Ensure group01 is not on the list of groups sudorule execute as, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       runasgroup:
         - group01
@@ -173,6 +192,7 @@
   - name: Ensure sudorule is present, with usercategory 'all'
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       usercategory: all
     register: result
@@ -181,6 +201,7 @@
   - name: Ensure sudorule is present, with usercategory 'all', again
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       usercategory: all
     register: result
@@ -189,6 +210,7 @@
   - name: Ensure sudorule is with usercategory 'all' is absent
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       state: absent
     register: result
@@ -197,6 +219,7 @@
   - name: Ensure sudorule is present, with runasusercategory 'all'.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       runasusercategory: all
     register: result
@@ -205,6 +228,7 @@
   - name: Ensure sudorule is present, with runasusercategory 'all', again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       runasusercategory: all
     register: result
@@ -213,6 +237,7 @@
   - name: Ensure sudorule is with runasusercategory 'all' is absent
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       state: absent
     register: result
@@ -221,6 +246,7 @@
   - name: Ensure sudorule is present, with runasgroupcategory 'all'.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       runasgroupcategory: all
     register: result
@@ -229,6 +255,7 @@
   - name: Ensure sudorule is present, with runasgroupcategory 'all', again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       runasgroupcategory: all
     register: result
@@ -237,6 +264,7 @@
   - name: Ensure sudorule is with runasgroupcategory 'all' is absent
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       state: absent
     register: result
@@ -245,6 +273,7 @@
   - name: Ensure sudorule is present, with usercategory 'all'.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       usercategory: all
     register: result
@@ -253,6 +282,7 @@
   - name: Ensure sudorule is present, with usercategory 'all', again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       usercategory: all
     register: result
@@ -261,6 +291,7 @@
   - name: Ensure sudorule is present, with hostategory 'all'
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allhosts
       hostcategory: all
     register: result
@@ -269,6 +300,7 @@
   - name: Ensure sudorule is present, with hostategory 'all', again
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allhosts
       hostcategory: all
     register: result
@@ -277,6 +309,7 @@
   - name: Ensure sudorule is disabled
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       state: disabled
     register: result
@@ -285,6 +318,7 @@
   - name: Ensure sudorule is disabled, again
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       state: disabled
     register: result
@@ -293,6 +327,7 @@
   - name: Ensure sudorule is enabled
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       state: enabled
     register: result
@@ -301,6 +336,7 @@
   - name: Ensure sudorule is enabled, again
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       state: enabled
     register: result
@@ -309,6 +345,7 @@
   - name: Ensure user is present in sudorule.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       user: user01
       action: member
@@ -318,6 +355,7 @@
   - name: Ensure user is present in sudorule, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       user: user01
       action: member
@@ -327,6 +365,7 @@
   - name: Ensure user is absent from sudorule.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       user: user01
       action: member
@@ -337,6 +376,7 @@
   - name: Ensure user is absent from sudorule, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       user: user01
       action: member
@@ -347,6 +387,7 @@
   - name: Ensure group is present in sudorule.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       group: group01
       action: member
@@ -356,6 +397,7 @@
   - name: Ensure group is present in sudorule, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       group: group01
       action: member
@@ -365,6 +407,7 @@
   - name: Ensure group is absent from sudorule.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       group: group01
       action: member
@@ -375,6 +418,7 @@
   - name: Ensure group is absent from sudorule, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       group: group01
       action: member
@@ -385,6 +429,7 @@
   - name: Ensure sudorule has a sudooption.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       sudooption: '!authenticate'
       action: member
@@ -394,6 +439,7 @@
   - name: Ensure sudorule has a sudooption, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       sudooption: '!authenticate'
       action: member
@@ -403,6 +449,7 @@
   - name: Ensure sudorule has an order.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       order: 1
     register: result
@@ -411,6 +458,7 @@
   - name: Ensure sudorule has an order, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       order: 1
     register: result
@@ -419,6 +467,7 @@
   - name: Ensure sudorule has another order.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       order: 10
     register: result
@@ -427,6 +476,7 @@
   - name: Ensure sudorule is present and some sudocmd are allowed.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       allow_sudocmd:
       - /sbin/ifconfig
@@ -437,6 +487,7 @@
   - name: Ensure sudorule is present and some sudocmd are allowed, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       allow_sudocmd:
       - /sbin/ifconfig
@@ -447,6 +498,7 @@
   - name: Ensure sudorule is present and some sudocmd are denyed.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       deny_sudocmd:
       - /usr/bin/vim
@@ -457,6 +509,7 @@
   - name: Ensure sudorule is present and some sudocmd are denyed, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       deny_sudocmd:
       - /usr/bin/vim
@@ -467,6 +520,7 @@
   - name: Ensure sudorule is present and, sudocmds are absent.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       allow_sudocmd: /sbin/ifconfig
       deny_sudocmd: /usr/bin/vim
@@ -478,6 +532,7 @@
   - name: Ensure sudorule is present and, sudocmds are absent, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       allow_sudocmd: /sbin/ifconfig
       deny_sudocmd: /usr/bin/vim
@@ -489,6 +544,7 @@
   - name: Ensure sudorule is present with cmdcategory 'all'.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allcommands
       cmdcategory: all
     register: result
@@ -497,6 +553,7 @@
   - name: Ensure sudorule is present with cmdcategory 'all', again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allcommands
       cmdcategory: all
     register: result
@@ -505,6 +562,7 @@
   - name: Ensure host "{{ ansible_facts['fqdn'] }}" is present in sudorule.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       host: "{{ ansible_facts['fqdn'] }}"
       action: member
@@ -514,6 +572,7 @@
   - name: Ensure host "{{ ansible_facts['fqdn'] }}" is present in sudorule, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       host: "{{ ansible_facts['fqdn'] }}"
       action: member
@@ -523,6 +582,7 @@
   - name: Ensure hostgroup is present in sudorule.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       hostgroup: cluster
       action: member
@@ -532,6 +592,7 @@
   - name: Ensure hostgroup is present in sudorule, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       hostgroup: cluster
       action: member
@@ -541,6 +602,7 @@
   - name: Ensure sudorule is present, with an allow_sudocmdgroup.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       allow_sudocmdgroup: test_sudorule
       state: present
@@ -550,6 +612,7 @@
   - name: Ensure sudorule is present, with an allow_sudocmdgroup, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       allow_sudocmdgroup: test_sudorule
       state: present
@@ -559,6 +622,7 @@
   - name: Ensure sudorule is present, but allow_sudocmdgroup is absent.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       allow_sudocmdgroup: test_sudorule
       action: member
@@ -569,6 +633,7 @@
   - name: Ensure sudorule is present, but allow_sudocmdgroup is absent.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       allow_sudocmdgroup: test_sudorule
       action: member
@@ -579,6 +644,7 @@
   - name: Ensure sudorule is present, with an deny_sudocmdgroup.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       deny_sudocmdgroup: test_sudorule
       state: present
@@ -588,6 +654,7 @@
   - name: Ensure sudorule is present, with an deny_sudocmdgroup, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       deny_sudocmdgroup: test_sudorule
       state: present
@@ -597,6 +664,7 @@
   - name: Ensure sudorule is present, but deny_sudocmdgroup is absent.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       deny_sudocmdgroup: test_sudorule
       action: member
@@ -607,6 +675,7 @@
   - name: Ensure sudorule is present, but deny_sudocmdgroup is absent, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       deny_sudocmdgroup: test_sudorule
       action: member
@@ -617,6 +686,7 @@
   - name: Ensure sudorule is absent
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       state: absent
     register: result
@@ -625,6 +695,7 @@
   - name: Ensure sudorule is absent, again.
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: testrule1
       state: absent
     register: result
@@ -633,6 +704,7 @@
   - name: Ensure sudorule allhosts is absent
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allhosts
       state: absent
     register: result
@@ -641,6 +713,7 @@
   - name: Ensure sudorule allhosts is absent, again
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allhosts
       state: absent
     register: result
@@ -649,6 +722,7 @@
   - name: Ensure sudorule allusers is absent
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       state: absent
     register: result
@@ -657,6 +731,7 @@
   - name: Ensure sudorule allusers is absent, again
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allusers
       state: absent
     register: result
@@ -665,6 +740,7 @@
   - name: Ensure sudorule allcommands is absent
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allcommands
       state: absent
     register: result
@@ -673,6 +749,7 @@
   - name: Ensure sudorule allcommands is absent, again
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: allcommands
       state: absent
     register: result
@@ -682,12 +759,14 @@
   - name : Ensure sudocmdgroup is absent
     ipasudocmdgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: test_sudorule
       state: absent
 
   - name: Ensure sudocmds are absent
     ipasudocmd:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - /sbin/ifconfig
       - /usr/bin/vim
@@ -696,6 +775,7 @@
   - name: Ensure sudorules are absent
     ipasudorule:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - testrule1
       - allusers
@@ -706,5 +786,6 @@
   - name: Ensure hostgroup is absent.
     ipahostgroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: cluster
       state: absent
diff --git a/tests/sudorule/test_sudorule_client_context.yml b/tests/sudorule/test_sudorule_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..65647cd9899705954cfae028681aa0fd83343a92
--- /dev/null
+++ b/tests/sudorule/test_sudorule_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test sudorule
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipasudorule:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test sudorule using client context, in client host.
+  import_playbook: test_sudorule.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test sudorule using client context, in server host.
+  import_playbook: test_sudorule.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/trust/test_trust.yml b/tests/trust/test_trust.yml
index 581c6828d04d8cc0f2b965c388e3a28eb77c3b31..e4ecdf501dc1cf078eebc7b7d431396825543fa4 100644
--- a/tests/trust/test_trust.yml
+++ b/tests/trust/test_trust.yml
@@ -1,6 +1,6 @@
 ---
 - name: find trust
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -11,6 +11,7 @@
     - name: delete trust
       ipatrust:
         ipaadmin_password: SomeADMINpassword
+        ipaapi_context: "{{ ipa_context | default(omit) }}"
         realm: windows.local
         state: absent
       register: del_trust
@@ -38,6 +39,7 @@
     - name: add trust
       ipatrust:
         ipaadmin_password: SomeADMINpassword
+        ipaapi_context: "{{ ipa_context | default(omit) }}"
         realm: windows.local
         admin: Administrator
         password: secret_ad_pw
diff --git a/tests/trust/test_trust_client_context.yml b/tests/trust/test_trust_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0b53cc53db9db6e8cceebb1e1a47082df6622554
--- /dev/null
+++ b/tests/trust/test_trust_client_context.yml
@@ -0,0 +1,38 @@
+---
+- name: Test trust
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipatrust:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      realm: windows.local
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test trust using client context, in client host.
+  import_playbook: test_trust.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test trust using client context, in server host.
+  import_playbook: test_trust.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
+  
diff --git a/tests/user/test_user.yml b/tests/user/test_user.yml
index 5246d14cda0c036ab724035b2388dc8b31827c4d..571a7bab82c09890ec29973d795eda9eff02d48c 100644
--- a/tests/user/test_user.yml
+++ b/tests/user/test_user.yml
@@ -1,6 +1,6 @@
 ---
 - name: Test user
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
   become: true
   gather_facts: false
 
@@ -8,12 +8,14 @@
   - name: Remove test users
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: manager1,manager2,manager3,pinky,pinky2
       state: absent
 
   - name: User manager1 present
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: manager1
       first: Manager
       last: One
@@ -23,6 +25,7 @@
   - name: User manager2 present
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: manager2
       first: Manager
       last: One
@@ -32,6 +35,7 @@
   - name: User manager3 present
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: manager3
       first: Manager
       last: One
@@ -41,6 +45,7 @@
   - name: User pinky present
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       uid: 10001
       gid: 100
@@ -84,6 +89,7 @@
   - name: User pinky present with changed settings
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       first: pinky
       last: Acme
@@ -98,6 +104,7 @@
   - name: User pinky add manager manager1
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       manager: manager1
       action: member
@@ -107,6 +114,7 @@
   - name: User pinky add manager manager1 again
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       manager: manager1
       action: member
@@ -116,6 +124,7 @@
   - name: User pinky add manager manager2, manager3
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       manager: manager2,manager3
       action: member
@@ -125,6 +134,7 @@
   - name: User pinky add manager manager2, manager3 again
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       manager: manager2,manager3
       action: member
@@ -134,6 +144,7 @@
   - name: User pinky remove manager manager1
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       manager: manager1
       action: member
@@ -144,6 +155,7 @@
   - name: User pinky remove manager manager1 again
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       manager: manager1
       action: member
@@ -154,6 +166,7 @@
   - name: User pinky add principal pa
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       principal: pa
       action: member
@@ -163,6 +176,7 @@
   - name: User pinky add principal pa again
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       principal: pa
       action: member
@@ -172,6 +186,7 @@
   - name: User pinky add principal pa1
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       principal: pa1
       action: member
@@ -181,6 +196,7 @@
   - name: User pinky remove principal pa1
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       principal: pa1
       action: member
@@ -191,6 +207,7 @@
   - name: User pinky remove principal pa1 again
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       principal: pa1
       action: member
@@ -201,6 +218,7 @@
   - name: User pinky remove principal pa
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       principal: pa
       action: member
@@ -211,6 +229,7 @@
   - name: User pinky remove principal non-existing pa2
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       principal: pa2
       action: member
@@ -221,6 +240,7 @@
   - name: User pinky absent and preserved
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       preserve: yes
       state: absent
@@ -230,6 +250,7 @@
   - name: User pinky undeleted (preserved before)
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       state: undeleted
     register: result
@@ -238,6 +259,7 @@
   - name: Users pinky disabled
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       state: disabled
     register: result
@@ -246,6 +268,7 @@
   - name: User pinky enabled
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: pinky
       state: enabled
     register: result
@@ -254,5 +277,6 @@
   - name: Remove test users
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: manager1,manager2,manager3,pinky,pinky2
       state: absent
diff --git a/tests/user/test_user_client_context.yml b/tests/user/test_user_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d09b68831f6434f5618bf846050b7869807e9a9d
--- /dev/null
+++ b/tests/user/test_user_client_context.yml
@@ -0,0 +1,37 @@
+---
+- name: Test user
+  hosts: ipaclients, ipaserver
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test user using client context, in client host.
+  import_playbook: test_user.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test user using client context, in server host.
+  import_playbook: test_user.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']
diff --git a/tests/vault/env_cleanup.yml b/tests/vault/env_cleanup.yml
index 9b0d6f7e57fb5f1d691fecf1864db05815f6aff7..e545e791b190122833d8a4b4c1b1abe6a38b553b 100644
--- a/tests/vault/env_cleanup.yml
+++ b/tests/vault/env_cleanup.yml
@@ -26,6 +26,7 @@
   - name: Ensure test users do not exist.
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name:
       - user01
       - user02
@@ -35,6 +36,7 @@
   - name: Ensure test groups do not exist.
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: vaultgroup
       state: absent
 
diff --git a/tests/vault/env_setup.yml b/tests/vault/env_setup.yml
index 059caf5f7a95d6327a14be2777b7ca25f2ec12bb..4e2d40e84e8cea5cb3cb5e95bf724fd283ecfd7b 100644
--- a/tests/vault/env_setup.yml
+++ b/tests/vault/env_setup.yml
@@ -35,11 +35,13 @@
   - name: Ensure vaultgroup exists.
     ipagroup:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: vaultgroup
 
   - name: Ensure testing users exist.
     ipauser:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       users:
       - name: user01
         first: First
diff --git a/tests/vault/test_vault_client_context.yml b/tests/vault/test_vault_client_context.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2ebb410e9633aed52ac1dc22374daca7065b63dc
--- /dev/null
+++ b/tests/vault/test_vault_client_context.yml
@@ -0,0 +1,25 @@
+---
+- name: Test vault
+  hosts: ipaserver
+  become: no
+  # Need to gather facts for ansible_env.
+  gather_facts: yes
+
+  tasks:
+  - name: Setup testing environment.
+    import_tasks: env_setup.yml
+
+  # vault requires 'ipaapi_context: client', and uses this
+  # context by defoult, so we test only for the case where
+  # 'ipaapi_context: server' is explicitly set.
+  - name: Execute with server context.
+    ipavault:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+      vault_type: standard
+    register: result
+    failed_when: not (result.failed and result.msg is regex("Context 'server' for ipavault not yet supported."))
+
+  - name: Cleanup testing environment.
+    import_tasks: env_cleanup.yml
diff --git a/utils/new_module b/utils/new_module
index b6eecb27f8b9a8544b0eeb158a2600703834c70b..882a9677f65ba02bd5e2e06b8bcd8bd0cc249da6 100755
--- a/utils/new_module
+++ b/utils/new_module
@@ -183,3 +183,4 @@ mkdir -p $dest
 src=test_module.yml.in
 [ $member == 1 ] && src=test_module+member.yml.in
 template $src $dest/test_$name.yml
+template test_module_client_context.yml.in $dest/test_${name}_client_context.yml
diff --git a/utils/templates/test_module+member.yml.in b/utils/templates/test_module+member.yml.in
index f7c5b3d5eea0568b13ff5fa0c7ab14ec47d3814f..01012078e1b0e45897230f8f07797b73023bdf51 100644
--- a/utils/templates/test_module+member.yml.in
+++ b/utils/templates/test_module+member.yml.in
@@ -1,7 +1,10 @@
 ---
 - name: Test $name
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
+  # Change "become" or "gather_facts" to "yes",
+  # if you test playbook requires any.
   become: no
+  gather_facts: no
 
   tasks:
 
@@ -20,6 +23,7 @@
   - name: Ensure $name NAME is present
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       # Add needed parameters here
     register: result
@@ -28,6 +32,7 @@
   - name: Ensure $name NAME is present again
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       # Add needed parameters here
     register: result
@@ -36,6 +41,7 @@
   - name: Ensure $name NAME member PARAMETER2 VALUE is present
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       PARAMETER2: VALUE
       action: member
@@ -45,6 +51,7 @@
   - name: Ensure $name NAME member PARAMETER2 VALUE is present again
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       PARAMETER2: VALUE
       action: member
@@ -54,6 +61,7 @@
   - name: Ensure $name NAME member PARAMETER2 VALUE is absent
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       PARAMETER2: VALUE
       action: member
@@ -64,6 +72,7 @@
   - name: Ensure $name NAME member PARAMETER2 VALUE is absent again
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       PARAMETER2: VALUE
       action: member
@@ -76,6 +85,7 @@
   - name: Ensure $name NAME is absent
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       state: absent
     register: result
@@ -84,6 +94,7 @@
   - name: Ensure $name NAME is absent again
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       state: absent
     register: result
@@ -94,5 +105,6 @@
   - name: Ensure $name NAME is absent
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       state: absent
diff --git a/utils/templates/test_module.yml.in b/utils/templates/test_module.yml.in
index 0286813b89a1de6380bb95aac95744d5f1ea3738..2ad53cc240d7a605de0bef0ac687d50fd435b6f4 100644
--- a/utils/templates/test_module.yml.in
+++ b/utils/templates/test_module.yml.in
@@ -1,7 +1,10 @@
 ---
 - name: Test $name
-  hosts: ipaserver
+  hosts: "{{ ipa_test_host | default('ipaserver') }}"
+  # Change "become" or "gather_facts" to "yes",
+  # if you test playbook requires any.
   become: no
+  gather_facts: no
 
   tasks:
 
@@ -10,6 +13,7 @@
   - name: Ensure $name NAME is absent
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       state: absent
 
@@ -28,6 +32,7 @@
   - name: Ensure $name NAME is present again
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       # Add needed parameters here
     register: result
@@ -38,6 +43,7 @@
   - name: Ensure $name NAME is absent
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       state: absent
     register: result
@@ -46,6 +52,7 @@
   - name: Ensure $name NAME is absent again
     ipa$name:
       ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
       name: NAME
       state: absent
     register: result
diff --git a/utils/templates/test_module_client_context.yml.in b/utils/templates/test_module_client_context.yml.in
new file mode 100644
index 0000000000000000000000000000000000000000..ee8c6789708e772bbda11cc155a0c5bd199f642b
--- /dev/null
+++ b/utils/templates/test_module_client_context.yml.in
@@ -0,0 +1,39 @@
+---
+- name: Test ${name}
+  hosts: ipaclients, ipaserver
+  # Change "become" or "gather_facts" to "yes",
+  # if you test playbook requires any.
+  become: no
+  gather_facts: no
+
+  tasks:
+  - name: Include FreeIPA facts.
+    include_tasks: ../env_freeipa_facts.yml
+
+  # Test will only be executed if host is not a server.
+  - name: Execute with server context in the client.
+    ipa${name}:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: server
+      name: ThisShouldNotWork
+    register: result
+    failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
+    when: ipa_host_is_client
+
+# Import basic module tests, and execute with ipa_context set to 'client'.
+# If ipaclients is set, it will be executed using the client, if not,
+# ipaserver will be used.
+#
+# With this setup, tests can be executed against an IPA client, against
+# an IPA server using "client" context, and ensure that tests are executed
+# in upstream CI.
+
+- name: Test ${name} using client context, in client host.
+  import_playbook: test_${name}.yml
+  when: groups['ipaclients']
+  vars:
+    ipa_test_host: ipaclients
+
+- name: Test ${name} using client context, in server host.
+  import_playbook: test_${name}.yml
+  when: groups['ipaclients'] is not defined or not groups['ipaclients']