Due to it's out of order execution you can easily get into race condition between different modules. You have to be very careful declaring pre-requisites for the tasks so they don't step on top of each other. On the other hand when you get this lets you deploy things much faster than straight line execution tools
Puppet's UI is very useful. It allows real-time control of managed nodes by using modules and configuration recipes that are on the master servers. Although the UI is great for management, it lacks when trying to configure modules.
Puppet is very mature and relatively old. This means that it has gathered quite a following over the years. This large community means that there are a lot of modules, guides and configuration recipes ready to use built by the community.
Puppet is a complete solution in terms of available features and modules. It has full support for all the main Operating Systems and provides lots of tools for its users.
You define the state the server should be in and Puppet transforms it that state. This is opposed to explicitly declaring a list of actions to be performed.
If a developer wants more flexibility and control there's always the option of falling back to explicitly running commands but that's discouraged.
Although you may need to preinstall favorite programming environment, Ansible modules are accessed via shell calls and therefore any executable on the remote system built for use with Ansible may be used as an Ansible module.
Ansible has a Web UI in the form of AnsibleWorks AWX which unfortunately does not tie directly into the CLI. So configuration elements present in the CLI can not appear in the UI unless a sync pass is run. Although the Web UI is helpful and functional, it's still not as complete feature-wise as the CLI.
Modules that support rich diffs can expose nearly every detail of what will change. However not all core modules support diffs, so there may still be some opaque chances made.
Some simple tasks such as triggering updates or reboots, or even checking if the service is running can be done without configuration files. These commands can be run from the command line instead.
Ansible is very easy to customize. It doesn't force you to use a language with which you are unfamiliar. Instead, all commands are packaged into YAML modules which are called playbooks. So as long as you use a programming language that can output JSON, you are able to customize it.
Ansible does not use agents. Instead, all master-agent communications are handled via SSH commands or through the Paramiko module which provides a Python interface to SSH.
Ansible is agentless, making it quick and painless to setup. Ansible has clear and detailed documentation and provides plenty of built-in modules. Its DSL is obtained using YAML and a familiar template system.
Ansible is still relatively new, as far as server automation tools go. This is the reason that many users have found it's documentation lacking in some parts. Although this is mitigated by the fact that it's very easy to learn to use.
Chef was released in 2009, which is relatively a long time ago for software. Since then it has been through several versions and many bug fixes and tests. All of this can make Chef more appealing to teams who are looking for stability and maturity, which are things that Chef brings on the table.
Chef has a relatively large community. One of the reasons for it is the fact that it's a pretty old and mature tool. Chef, originally released in 2009, is a more mature product. Being popular and with a large and dedicated community means that Chef has lots and lots of resources and guides from third party sources out there for beginners to pick up. Not only that, there are also many plugins and configuration recipes made by the community.
Chef has a steeper learning curve than many of its competitors, making it a more difficult tool for the non-devs of a team (such as sysadmins) to work with. For some teams, the added cost of teaching Chef to the team may outweigh the benefits.